mirror of
https://github.com/lantean-code/qbtmud.git
synced 2025-11-02 21:13:15 +00:00
Update project name and namespaces
This commit is contained in:
28
Lantean.QBTMud/Components/ApplicationActions.razor
Normal file
28
Lantean.QBTMud/Components/ApplicationActions.razor
Normal file
@@ -0,0 +1,28 @@
|
||||
@if (IsMenu)
|
||||
{
|
||||
@foreach (var action in Actions)
|
||||
{
|
||||
if (action.SeparatorBefore)
|
||||
{
|
||||
<MudDivider />
|
||||
}
|
||||
<MudMenuItem Icon="@action.Icon" IconColor="@action.Color" Href="@action.Href">@action.Text</MudMenuItem>
|
||||
}
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Undo" OnClick="ResetWebUI">Reset Web UI</MudMenuItem>
|
||||
<MudDivider />
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Logout" OnClick="Logout">Logout</MudMenuItem>
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.ExitToApp" OnClick="Exit">Exit qBittorrent</MudMenuItem>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudNavLink Icon="@Icons.Material.Outlined.Navigation" OnClick="NavigateBack">Torrents</MudNavLink>
|
||||
<MudDivider />
|
||||
@foreach (var action in Actions)
|
||||
{
|
||||
if (action.SeparatorBefore)
|
||||
{
|
||||
<MudDivider />
|
||||
}
|
||||
<MudNavLink Icon="@action.Icon" IconColor="@action.Color" Href="@action.Href">@action.Text</MudNavLink>
|
||||
}
|
||||
}
|
||||
94
Lantean.QBTMud/Components/ApplicationActions.razor.cs
Normal file
94
Lantean.QBTMud/Components/ApplicationActions.razor.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBitTorrentClient.Models;
|
||||
using Lantean.QBTMud.Helpers;
|
||||
using Lantean.QBTMud.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMud.Components
|
||||
{
|
||||
public partial class ApplicationActions
|
||||
{
|
||||
private List<UIAction>? _actions;
|
||||
|
||||
[Inject]
|
||||
protected NavigationManager NavigationManager { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected IDialogService DialogService { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected IApiClient ApiClient { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public bool IsMenu { get; set; }
|
||||
|
||||
[Parameter]
|
||||
[EditorRequired]
|
||||
public Preferences? Preferences { get; set; }
|
||||
|
||||
protected IEnumerable<UIAction> Actions => GetActions();
|
||||
|
||||
private IEnumerable<UIAction> GetActions()
|
||||
{
|
||||
if (_actions is not null)
|
||||
{
|
||||
foreach (var action in _actions)
|
||||
{
|
||||
if (action.Name != "rss" || Preferences is not null && Preferences.RssProcessingEnabled)
|
||||
{
|
||||
yield return action;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
_actions =
|
||||
[
|
||||
new("statistics", "Statistics", Icons.Material.Filled.PieChart, Color.Default, "/statistics"),
|
||||
new("search", "Search", Icons.Material.Filled.Search, Color.Default, "/search"),
|
||||
new("rss", "RSS", Icons.Material.Filled.RssFeed, Color.Default, "/rss"),
|
||||
new("log", "Execution Log", Icons.Material.Filled.List, Color.Default, "/log"),
|
||||
new("blocks", "Blocked IPs", Icons.Material.Filled.DisabledByDefault, Color.Default, "/blocks"),
|
||||
new("tags", "Tag Management", Icons.Material.Filled.Label, Color.Default, "/tags", separatorBefore: true),
|
||||
new("categories", "Category Management", Icons.Material.Filled.List, Color.Default, "/categories"),
|
||||
new("settings", "Settings", Icons.Material.Filled.Settings, Color.Default, "/settings", separatorBefore: true),
|
||||
new("about", "About", Icons.Material.Filled.Info, Color.Default, "/about"),
|
||||
];
|
||||
}
|
||||
|
||||
protected void NavigateBack()
|
||||
{
|
||||
NavigationManager.NavigateTo("/");
|
||||
}
|
||||
|
||||
protected async Task ResetWebUI()
|
||||
{
|
||||
var preferences = new UpdatePreferences
|
||||
{
|
||||
AlternativeWebuiEnabled = false,
|
||||
};
|
||||
|
||||
await ApiClient.SetApplicationPreferences(preferences);
|
||||
|
||||
NavigationManager.NavigateTo("/", true);
|
||||
}
|
||||
|
||||
protected async Task Logout()
|
||||
{
|
||||
await DialogService.ShowConfirmDialog("Logout?", "Are you sure you want to logout?", async () =>
|
||||
{
|
||||
await ApiClient.Logout();
|
||||
|
||||
NavigationManager.NavigateTo("/", true);
|
||||
});
|
||||
}
|
||||
|
||||
protected async Task Exit()
|
||||
{
|
||||
await DialogService.ShowConfirmDialog("Quit?", "Are you sure you want to exit qBittorrent?", ApiClient.Shutdown);
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Lantean.QBTMud/Components/Dialogs/AddPeerDialog.razor
Normal file
27
Lantean.QBTMud/Components/Dialogs/AddPeerDialog.razor
Normal file
@@ -0,0 +1,27 @@
|
||||
@inherits SubmittableDialog
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<table width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width: 70%"><MudTextField T="string" Label="IP" Value="@IP" ValueChanged="SetIP" Required Variant="Variant.Outlined" /></td>
|
||||
<td style="width: 30%"><MudNumericField T="int?" Label="Port" Value="@Port" ValueChanged="SetPort" Required Variant="Variant.Outlined" /></td>
|
||||
<td><MudIconButton Icon="@Icons.Material.Filled.Add" OnClick="AddTracker" /></td>
|
||||
</tr>
|
||||
@foreach (var peer in Peers)
|
||||
{
|
||||
var peerRef = peer;
|
||||
<tr>
|
||||
<td>@peer</td>
|
||||
<td><MudIconButton Icon="@Icons.Material.Filled.Delete" OnClick="@(e => DeletePeer(peerRef))" /></td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Cancel</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="Submit">Save</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
62
Lantean.QBTMud/Components/Dialogs/AddPeerDialog.razor.cs
Normal file
62
Lantean.QBTMud/Components/Dialogs/AddPeerDialog.razor.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using Lantean.QBitTorrentClient.Models;
|
||||
using Lantean.QBTMud.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMud.Components.Dialogs
|
||||
{
|
||||
public partial class AddPeerDialog
|
||||
{
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
protected HashSet<PeerId> Peers { get; } = [];
|
||||
|
||||
protected string? IP { get; set; }
|
||||
|
||||
protected int? Port { get; set; }
|
||||
|
||||
protected void AddTracker()
|
||||
{
|
||||
if (string.IsNullOrEmpty(IP) || !Port.HasValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Peers.Add(new PeerId(IP, Port.Value));
|
||||
IP = null;
|
||||
Port = null;
|
||||
}
|
||||
|
||||
protected void SetIP(string value)
|
||||
{
|
||||
IP = value;
|
||||
}
|
||||
|
||||
protected void SetPort(int? value)
|
||||
{
|
||||
Port = value;
|
||||
}
|
||||
|
||||
protected void DeletePeer(PeerId peer)
|
||||
{
|
||||
Peers.Remove(peer);
|
||||
}
|
||||
|
||||
protected void Cancel()
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
|
||||
protected void Submit()
|
||||
{
|
||||
MudDialog.Close(Peers);
|
||||
}
|
||||
|
||||
protected override Task Submit(KeyboardEvent keyboardEvent)
|
||||
{
|
||||
Submit();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
26
Lantean.QBTMud/Components/Dialogs/AddTagDialog.razor
Normal file
26
Lantean.QBTMud/Components/Dialogs/AddTagDialog.razor
Normal file
@@ -0,0 +1,26 @@
|
||||
@inherits SubmittableDialog
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<table width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width: 100%"><MudTextField T="string" Label="Tag" Value="@Tag" ValueChanged="SetTag" Required Variant="Variant.Outlined" /></td>
|
||||
<td><MudIconButton Icon="@Icons.Material.Filled.Add" OnClick="AddTag" /></td>
|
||||
</tr>
|
||||
@foreach (var tag in Tags)
|
||||
{
|
||||
var tagRef = tag;
|
||||
<tr>
|
||||
<td>@tag</td>
|
||||
<td><MudIconButton Icon="@Icons.Material.Filled.Delete" OnClick="@(e => DeleteTag(tagRef))" /></td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Cancel</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="Submit">Save</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
64
Lantean.QBTMud/Components/Dialogs/AddTagDialog.razor.cs
Normal file
64
Lantean.QBTMud/Components/Dialogs/AddTagDialog.razor.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBTMud.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMud.Components.Dialogs
|
||||
{
|
||||
public partial class AddTagDialog
|
||||
{
|
||||
[Inject]
|
||||
protected IApiClient ApiClient { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected IDialogService DialogService { get; set; } = default!;
|
||||
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
protected HashSet<string> Tags { get; } = [];
|
||||
|
||||
protected string? Tag { get; set; }
|
||||
|
||||
protected void AddTag()
|
||||
{
|
||||
if (string.IsNullOrEmpty(Tag))
|
||||
{
|
||||
return;
|
||||
}
|
||||
Tags.Add(Tag);
|
||||
Tag = null;
|
||||
}
|
||||
|
||||
protected void SetTag(string tag)
|
||||
{
|
||||
Tag = tag;
|
||||
}
|
||||
|
||||
protected void DeleteTag(string tag)
|
||||
{
|
||||
Tags.Remove(tag);
|
||||
}
|
||||
|
||||
protected void Cancel()
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
|
||||
protected void Submit()
|
||||
{
|
||||
if (Tags.Count == 0 && Tag is not null)
|
||||
{
|
||||
Tags.Add(Tag);
|
||||
}
|
||||
MudDialog.Close(Tags);
|
||||
}
|
||||
|
||||
protected override Task Submit(KeyboardEvent keyboardEvent)
|
||||
{
|
||||
Submit();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
24
Lantean.QBTMud/Components/Dialogs/AddTorrentFileDialog.razor
Normal file
24
Lantean.QBTMud/Components/Dialogs/AddTorrentFileDialog.razor
Normal file
@@ -0,0 +1,24 @@
|
||||
@inherits SubmittableDialog
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudFileUpload T="IReadOnlyList<IBrowserFile>" FilesChanged="UploadFiles" Accept=".torrent" MaximumFileCount="50" >
|
||||
<ActivatorContent>
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.CloudUpload">
|
||||
Choose files
|
||||
</MudButton>
|
||||
</ActivatorContent>
|
||||
</MudFileUpload>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
<AddTorrentOptions @ref="TorrentOptions" ShowCookieOption="true" />
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Close</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="Submit">Upload Torrents</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
@@ -0,0 +1,40 @@
|
||||
using Lantean.QBTMud.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMud.Components.Dialogs
|
||||
{
|
||||
public partial class AddTorrentFileDialog
|
||||
{
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
protected IReadOnlyList<IBrowserFile> Files { get; set; } = [];
|
||||
|
||||
protected AddTorrentOptions TorrentOptions { get; set; } = default!;
|
||||
|
||||
protected void UploadFiles(IReadOnlyList<IBrowserFile> files)
|
||||
{
|
||||
Files = files;
|
||||
}
|
||||
|
||||
protected void Cancel()
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
|
||||
protected void Submit()
|
||||
{
|
||||
var options = new AddTorrentFileOptions(Files, TorrentOptions.GetTorrentOptions());
|
||||
MudDialog.Close(DialogResult.Ok(options));
|
||||
}
|
||||
|
||||
protected override Task Submit(KeyboardEvent keyboardEvent)
|
||||
{
|
||||
Submit();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
16
Lantean.QBTMud/Components/Dialogs/AddTorrentLinkDialog.razor
Normal file
16
Lantean.QBTMud/Components/Dialogs/AddTorrentLinkDialog.razor
Normal file
@@ -0,0 +1,16 @@
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudFocusTrap>
|
||||
<MudTextField @ref="UrlsTextField" Label="Urls" Lines="10" @bind-Value="Urls" Variant="Variant.Outlined" AutoFocus="true" />
|
||||
</MudFocusTrap>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
<AddTorrentOptions @ref="TorrentOptions" ShowCookieOption="false" />
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Close</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="Submit">Upload Torrents</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
@@ -0,0 +1,93 @@
|
||||
using Lantean.QBTMud.Models;
|
||||
using Lantean.QBTMud.Services;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMud.Components.Dialogs
|
||||
{
|
||||
public partial class AddTorrentLinkDialog : IAsyncDisposable
|
||||
{
|
||||
private bool _disposedValue;
|
||||
|
||||
private readonly KeyboardEvent _ctrlEnterKey = new KeyboardEvent("Enter")
|
||||
{
|
||||
CtrlKey = true,
|
||||
};
|
||||
|
||||
[Inject]
|
||||
protected IKeyboardService KeyboardService { get; set; } = default!;
|
||||
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public string? Url { get; set; }
|
||||
|
||||
protected MudTextField<string?>? UrlsTextField { get; set; }
|
||||
|
||||
protected string? Urls { get; set; }
|
||||
|
||||
protected AddTorrentOptions TorrentOptions { get; set; } = default!;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
if (Url is not null)
|
||||
{
|
||||
Urls = Url;
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
await KeyboardService.RegisterKeypressEvent(_ctrlEnterKey, Submit);
|
||||
await KeyboardService.Focus();
|
||||
}
|
||||
}
|
||||
|
||||
protected void Cancel()
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
|
||||
protected void Submit()
|
||||
{
|
||||
if (Urls is null)
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
return;
|
||||
}
|
||||
var options = new AddTorrentLinkOptions(Urls, TorrentOptions.GetTorrentOptions());
|
||||
MudDialog.Close(DialogResult.Ok(options));
|
||||
}
|
||||
|
||||
protected Task Submit(KeyboardEvent keyboardEvent)
|
||||
{
|
||||
Submit();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected virtual async ValueTask DisposeAsync(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
await KeyboardService.UnregisterKeypressEvent(_ctrlEnterKey);
|
||||
await KeyboardService.UnFocus();
|
||||
}
|
||||
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
await DisposeAsync(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
68
Lantean.QBTMud/Components/Dialogs/AddTorrentOptions.razor
Normal file
68
Lantean.QBTMud/Components/Dialogs/AddTorrentOptions.razor
Normal file
@@ -0,0 +1,68 @@
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudSwitch Label="Additional Options" @bind-Value="Expanded" LabelPosition="LabelPosition.End" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
<MudCollapse Expanded="Expanded">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudSelect Label="Torrent Management Mode" @bind-Value="TorrentManagementMode" Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="false">Manual</MudSelectItem>
|
||||
<MudSelectItem Value="true">Automatic</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField Label="Save files to location" @bind-Value="SavePath" Variant="Variant.Outlined"></MudTextField>
|
||||
</MudItem>
|
||||
@if (ShowCookieOption)
|
||||
{
|
||||
<MudItem xs="12">
|
||||
<MudTextField Label="Cookie" @bind-Value="Cookie" Variant="Variant.Outlined"></MudTextField>
|
||||
</MudItem>
|
||||
}
|
||||
<MudItem xs="12">
|
||||
<MudTextField Label="Rename" @bind-Value="RenameTorrent" Variant="Variant.Outlined"></MudTextField>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect Label="Category" @bind-Value="Category" Variant="Variant.Outlined">
|
||||
@foreach (var category in Categories)
|
||||
{
|
||||
<MudSelectItem Value="category">@category</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Start torrent" @bind-Value="StartTorrent" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Add to top of queue" @bind-Value="AddToTopOfQueue" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect Label="Stop condition" @bind-Value="StopCondition" Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="@("None")">None</MudSelectItem>
|
||||
<MudSelectItem Value="@("MetadataReceived")">Metadata received</MudSelectItem>
|
||||
<MudSelectItem Value="@("FilesChecked")">Files checked</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Skip hash check" @bind-Value="SkipHashCheck" />
|
||||
</MudItem>
|
||||
<MudSelect Label="Content layout" @bind-Value="ContentLayout" Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="@("Original")">Original</MudSelectItem>
|
||||
<MudSelectItem Value="@("Subfolder")">Create subfolder</MudSelectItem>
|
||||
<MudSelectItem Value="@("NoSubfolder")">Don't create subfolder'</MudSelectItem>
|
||||
</MudSelect>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Download in sequentual order" @bind-Value="DownloadInSequentialOrder" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Download first and last pieces first" @bind-Value="DownloadFirstAndLastPiecesFirst" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField Label="Limit download rate" @bind-Value="DownloadLimit" Variant="Variant.Outlined" Min="0" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField Label="Limit upload rate" @bind-Value="UploadLimit" Variant="Variant.Outlined" Min="0" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCollapse>
|
||||
81
Lantean.QBTMud/Components/Dialogs/AddTorrentOptions.razor.cs
Normal file
81
Lantean.QBTMud/Components/Dialogs/AddTorrentOptions.razor.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBTMud.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Lantean.QBTMud.Components.Dialogs
|
||||
{
|
||||
public partial class AddTorrentOptions
|
||||
{
|
||||
[Inject]
|
||||
protected IApiClient ApiClient { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public bool ShowCookieOption { get; set; }
|
||||
|
||||
protected bool Expanded { get; set; }
|
||||
|
||||
protected bool TorrentManagementMode { get; set; }
|
||||
|
||||
protected string SavePath { get; set; } = default!;
|
||||
|
||||
protected string? Cookie { get; set; }
|
||||
|
||||
protected string? RenameTorrent { get; set; }
|
||||
|
||||
protected IEnumerable<string> Categories { get; set; } = [];
|
||||
|
||||
protected string? Category { get; set; }
|
||||
|
||||
protected bool StartTorrent { get; set; } = true;
|
||||
|
||||
protected bool AddToTopOfQueue { get; set; } = true;
|
||||
|
||||
protected string StopCondition { get; set; } = "None";
|
||||
|
||||
protected bool SkipHashCheck { get; set; } = false;
|
||||
|
||||
protected string ContentLayout { get; set; } = "Original";
|
||||
|
||||
protected bool DownloadInSequentialOrder { get; set; } = false;
|
||||
|
||||
protected bool DownloadFirstAndLastPiecesFirst { get; set; } = false;
|
||||
|
||||
protected long DownloadLimit { get; set; }
|
||||
|
||||
protected long UploadLimit { get; set; }
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var categories = await ApiClient.GetAllCategories();
|
||||
Categories = categories.Select(c => c.Key).ToList();
|
||||
|
||||
var preferences = await ApiClient.GetApplicationPreferences();
|
||||
|
||||
TorrentManagementMode = preferences.AutoTmmEnabled;
|
||||
SavePath = preferences.SavePath;
|
||||
StartTorrent = !preferences.StartPausedEnabled;
|
||||
AddToTopOfQueue = preferences.AddToTopOfQueue;
|
||||
StopCondition = preferences.TorrentStopCondition;
|
||||
ContentLayout = preferences.TorrentContentLayout;
|
||||
}
|
||||
|
||||
public TorrentOptions GetTorrentOptions()
|
||||
{
|
||||
return new TorrentOptions(
|
||||
TorrentManagementMode,
|
||||
SavePath,
|
||||
Cookie,
|
||||
RenameTorrent,
|
||||
Category,
|
||||
StartTorrent,
|
||||
AddToTopOfQueue,
|
||||
StopCondition,
|
||||
SkipHashCheck,
|
||||
ContentLayout,
|
||||
DownloadInSequentialOrder,
|
||||
DownloadFirstAndLastPiecesFirst,
|
||||
DownloadLimit,
|
||||
UploadLimit);
|
||||
}
|
||||
}
|
||||
}
|
||||
26
Lantean.QBTMud/Components/Dialogs/AddTrackerDialog.razor
Normal file
26
Lantean.QBTMud/Components/Dialogs/AddTrackerDialog.razor
Normal file
@@ -0,0 +1,26 @@
|
||||
@inherits SubmittableDialog
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<table width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width: 100%"><MudTextField T="string" Label="Tracker" Value="@Tracker" ValueChanged="SetTracker" Required Variant="Variant.Outlined" /></td>
|
||||
<td><MudIconButton Icon="@Icons.Material.Filled.Add" OnClick="AddTracker" /></td>
|
||||
</tr>
|
||||
@foreach (var tracker in Trackers)
|
||||
{
|
||||
var trackerRef = tracker;
|
||||
<tr>
|
||||
<td>@tracker</td>
|
||||
<td><MudIconButton Icon="@Icons.Material.Filled.Delete" OnClick="@(e => DeleteTracker(trackerRef))" /></td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Cancel</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="Submit">Save</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
53
Lantean.QBTMud/Components/Dialogs/AddTrackerDialog.razor.cs
Normal file
53
Lantean.QBTMud/Components/Dialogs/AddTrackerDialog.razor.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using Lantean.QBTMud.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMud.Components.Dialogs
|
||||
{
|
||||
public partial class AddTrackerDialog
|
||||
{
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
protected HashSet<string> Trackers { get; } = [];
|
||||
|
||||
protected string? Tracker { get; set; }
|
||||
|
||||
protected void AddTracker()
|
||||
{
|
||||
if (string.IsNullOrEmpty(Tracker))
|
||||
{
|
||||
return;
|
||||
}
|
||||
Trackers.Add(Tracker);
|
||||
Tracker = null;
|
||||
}
|
||||
|
||||
protected void SetTracker(string tracker)
|
||||
{
|
||||
Tracker = tracker;
|
||||
}
|
||||
|
||||
protected void DeleteTracker(string tracker)
|
||||
{
|
||||
Trackers.Remove(tracker);
|
||||
}
|
||||
|
||||
protected void Cancel()
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
|
||||
protected void Submit()
|
||||
{
|
||||
MudDialog.Close(Trackers);
|
||||
}
|
||||
|
||||
protected override Task Submit(KeyboardEvent keyboardEvent)
|
||||
{
|
||||
Submit();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
@inherits SubmittableDialog
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudTextField Label="Category" @bind-Value="Category" Required Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField Label="Save Path" @bind-Value="SavePath" Required Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Cancel</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="Submit">Add</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
@@ -0,0 +1,59 @@
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBTMud.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMud.Components.Dialogs
|
||||
{
|
||||
public partial class CategoryPropertiesDialog
|
||||
{
|
||||
private string _savePath = string.Empty;
|
||||
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected IApiClient ApiClient { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public string? Category { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string? SavePath { get; set; }
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var preferences = await ApiClient.GetApplicationPreferences();
|
||||
_savePath = preferences.SavePath;
|
||||
|
||||
SavePath ??= _savePath;
|
||||
}
|
||||
|
||||
protected void Cancel()
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
|
||||
protected void Submit()
|
||||
{
|
||||
if (Category is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(SavePath))
|
||||
{
|
||||
SavePath = _savePath;
|
||||
}
|
||||
|
||||
MudDialog.Close(DialogResult.Ok(new Category(Category, SavePath)));
|
||||
}
|
||||
|
||||
protected override Task Submit(KeyboardEvent keyboardEvent)
|
||||
{
|
||||
Submit();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
32
Lantean.QBTMud/Components/Dialogs/ColumnOptionsDialog.razor
Normal file
32
Lantean.QBTMud/Components/Dialogs/ColumnOptionsDialog.razor
Normal file
@@ -0,0 +1,32 @@
|
||||
@typeparam T
|
||||
@inherits SubmittableDialog
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudCard Class="w-100" Elevation="0">
|
||||
<MudGrid>
|
||||
@for (var i = 0; i < Columns.Count; i++)
|
||||
{
|
||||
var column = Columns[i];
|
||||
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))" />
|
||||
</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))" />
|
||||
</MudItem>
|
||||
<MudItem xs="1">
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.KeyboardArrowUp" Disabled="@(index == 0)" OnClick="@(e => MoveUp(index))" />
|
||||
</MudItem>
|
||||
<MudItem xs="1">
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.KeyboardArrowDown" Disabled="@(index == Columns.Count - 1)" OnClick="@(e => MoveDown(index))" />
|
||||
</MudItem>
|
||||
}
|
||||
</MudGrid>
|
||||
</MudCard>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Cancel</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="Submit">Save</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
154
Lantean.QBTMud/Components/Dialogs/ColumnOptionsDialog.razor.cs
Normal file
154
Lantean.QBTMud/Components/Dialogs/ColumnOptionsDialog.razor.cs
Normal file
@@ -0,0 +1,154 @@
|
||||
using Lantean.QBTMud.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMud.Components.Dialogs
|
||||
{
|
||||
public partial class ColumnOptionsDialog<T>
|
||||
{
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
[EditorRequired]
|
||||
public List<ColumnDefinition<T>> Columns { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
[EditorRequired]
|
||||
public HashSet<string> SelectedColumns { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public Dictionary<string, int?> Widths { get; set; } = [];
|
||||
|
||||
protected HashSet<string> SelectedColumnsInternal { get; set; } = [];
|
||||
|
||||
protected Dictionary<string, int?> WidthsInternal { get; set; } = [];
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
if (SelectedColumnsInternal.Count == 0)
|
||||
{
|
||||
if (SelectedColumns.Count != 0)
|
||||
{
|
||||
foreach (var selectedColumn in SelectedColumns)
|
||||
{
|
||||
SelectedColumnsInternal.Add(selectedColumn);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var column in Columns.Where(c => c.Enabled))
|
||||
{
|
||||
SelectedColumns.Add(column.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (WidthsInternal.Count == 0)
|
||||
{
|
||||
foreach (var width in Widths)
|
||||
{
|
||||
WidthsInternal[width.Key] = width.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void SetSelected(bool selected, string id)
|
||||
{
|
||||
if (selected)
|
||||
{
|
||||
SelectedColumnsInternal.Add(id);
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedColumnsInternal.Remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
protected void SetWidth(string? value, string id)
|
||||
{
|
||||
var column = Columns.Find(c => c.Id == id);
|
||||
var defaultWidth = column?.Width;
|
||||
|
||||
if (int.TryParse(value, out var width))
|
||||
{
|
||||
if (width == defaultWidth)
|
||||
{
|
||||
WidthsInternal.Remove(id);
|
||||
}
|
||||
else
|
||||
{
|
||||
WidthsInternal[id] = width;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (defaultWidth is null)
|
||||
{
|
||||
WidthsInternal.Remove(id);
|
||||
}
|
||||
else
|
||||
{
|
||||
WidthsInternal[id] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void MoveUp(int index)
|
||||
{
|
||||
if (index == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
(Columns[index], Columns[index - 1]) = (Columns[index - 1], Columns[index]);
|
||||
}
|
||||
|
||||
protected void MoveDown(int index)
|
||||
{
|
||||
if (index >= Columns.Count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
(Columns[index], Columns[index + 1]) = (Columns[index + 1], Columns[index]);
|
||||
}
|
||||
|
||||
protected string GetValue(int? value, string columnId)
|
||||
{
|
||||
if (WidthsInternal.TryGetValue(columnId, out var newWidth))
|
||||
{
|
||||
value = newWidth;
|
||||
}
|
||||
|
||||
if (!value.HasValue)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (value.Value <= 0)
|
||||
{
|
||||
return "auto";
|
||||
}
|
||||
|
||||
return value.Value.ToString();
|
||||
}
|
||||
|
||||
protected void Cancel()
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
|
||||
protected void Submit()
|
||||
{
|
||||
MudDialog.Close(DialogResult.Ok((SelectedColumnsInternal, WidthsInternal)));
|
||||
}
|
||||
|
||||
protected override Task Submit(KeyboardEvent keyboardEvent)
|
||||
{
|
||||
Submit();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Lantean.QBTMud/Components/Dialogs/ConfirmDialog.razor
Normal file
11
Lantean.QBTMud/Components/Dialogs/ConfirmDialog.razor
Normal file
@@ -0,0 +1,11 @@
|
||||
@inherits SubmittableDialog
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
@Content
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">@CancelText</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="Submit">@SuccessText</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
38
Lantean.QBTMud/Components/Dialogs/ConfirmDialog.razor.cs
Normal file
38
Lantean.QBTMud/Components/Dialogs/ConfirmDialog.razor.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using Lantean.QBTMud.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMud.Components.Dialogs
|
||||
{
|
||||
public partial class ConfirmDialog
|
||||
{
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public string Content { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public string? SuccessText { get; set; } = "Ok";
|
||||
|
||||
[Parameter]
|
||||
public string? CancelText { get; set; } = "Cancel";
|
||||
|
||||
protected void Cancel()
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
|
||||
protected void Submit()
|
||||
{
|
||||
MudDialog.Close(DialogResult.Ok(true));
|
||||
}
|
||||
|
||||
protected override Task Submit(KeyboardEvent keyboardEvent)
|
||||
{
|
||||
Submit();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
17
Lantean.QBTMud/Components/Dialogs/DeleteDialog.razor
Normal file
17
Lantean.QBTMud/Components/Dialogs/DeleteDialog.razor
Normal file
@@ -0,0 +1,17 @@
|
||||
@inherits SubmittableDialog
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudText>Are you sure you want to remove @Count torrent@(Count == 1 ? "": "s") from the transfer list?</MudText>
|
||||
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudCheckBox Label="Also permanently delete the files" @bind-Value="DeleteFiles" LabelPosition="LabelPosition.End" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Cancel</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="Submit">Remove</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
34
Lantean.QBTMud/Components/Dialogs/DeleteDialog.razor.cs
Normal file
34
Lantean.QBTMud/Components/Dialogs/DeleteDialog.razor.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using Lantean.QBTMud.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMud.Components.Dialogs
|
||||
{
|
||||
public partial class DeleteDialog
|
||||
{
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public int Count { get; set; }
|
||||
|
||||
protected bool DeleteFiles { get; set; }
|
||||
|
||||
protected void Cancel()
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
|
||||
protected void Submit()
|
||||
{
|
||||
MudDialog.Close(DialogResult.Ok(DeleteFiles));
|
||||
}
|
||||
|
||||
protected override Task Submit(KeyboardEvent keyboardEvent)
|
||||
{
|
||||
Submit();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
34
Lantean.QBTMud/Components/Dialogs/ExceptionDialog.razor
Normal file
34
Lantean.QBTMud/Components/Dialogs/ExceptionDialog.razor
Normal file
@@ -0,0 +1,34 @@
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudGrid>
|
||||
@if (Exception is null)
|
||||
{
|
||||
<MudItem xs="12">
|
||||
<MudAlert Severity="Severity.Error">
|
||||
Missing error information.
|
||||
</MudAlert>
|
||||
</MudItem>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudItem xs="12">
|
||||
<MudField Label="Message">@Exception.Message</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudField Label="Source">@Exception.Source</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudField Label="Stack Trace">
|
||||
<pre class="overflow">
|
||||
@Exception.StackTrace
|
||||
</pre>
|
||||
</MudField>
|
||||
</MudItem>
|
||||
}
|
||||
</MudGrid>
|
||||
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Close">Close</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
19
Lantean.QBTMud/Components/Dialogs/ExceptionDialog.razor.cs
Normal file
19
Lantean.QBTMud/Components/Dialogs/ExceptionDialog.razor.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMud.Components.Dialogs
|
||||
{
|
||||
public partial class ExceptionDialog
|
||||
{
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public Exception? Exception { get; set; }
|
||||
|
||||
protected void Close()
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
63
Lantean.QBTMud/Components/Dialogs/FilterOptionsDialog.razor
Normal file
63
Lantean.QBTMud/Components/Dialogs/FilterOptionsDialog.razor
Normal file
@@ -0,0 +1,63 @@
|
||||
@typeparam T
|
||||
@inherits SubmittableDialog
|
||||
|
||||
<MudDialog ContentStyle="mix-width: 400px">
|
||||
<DialogContent>
|
||||
<MudGrid>
|
||||
@foreach (var definition in FilterDefinitions ?? [])
|
||||
{
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Column">@definition.Column</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="3">
|
||||
<MudSelect Label="Operator" T="string" Value="@definition.Operator" ValueChanged="@(v => DefinitionOperatorChanged(definition, v))">
|
||||
@foreach (var op in Filter.FilterOperator.GetOperatorByDataType(definition.ColumnType))
|
||||
{
|
||||
<MudSelectItem T="string" Value="op">@op</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudTextField Label="Value" T="object" Value="@definition.Value" ValueChanged="@(v => DefinitionValueChanged(definition, v))" />
|
||||
</MudItem>
|
||||
<MudItem xs="1">
|
||||
<MudIconButton OnClick="@(e => RemoveDefinition(definition))" Icon="@Icons.Material.Outlined.Remove" />
|
||||
</MudItem>
|
||||
<MudDivider />
|
||||
}
|
||||
<MudItem xs="4">
|
||||
<MudSelect Label="Column" T="string" ValueChanged="ColumnChanged">
|
||||
@foreach (var propertyName in GetAvailablePropertyNames())
|
||||
{
|
||||
<MudSelectItem T="string" Value="@propertyName" />
|
||||
}
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="3">
|
||||
<MudSelect Label="Operator" T="string" ValueChanged="OperatorChanged">
|
||||
@if (ColumnType is null)
|
||||
{
|
||||
<MudSelectItem T="string" Value="@("")">Please select a column.</MudSelectItem>
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var op in Filter.FilterOperator.GetOperatorByDataType(ColumnType))
|
||||
{
|
||||
<MudSelectItem T="string" Value="op">@op</MudSelectItem>
|
||||
}
|
||||
}
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudTextField Label="Value" T="string" ValueChanged="ValueChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="1">
|
||||
<MudIconButton OnClick="AddDefinition" Icon="@Icons.Material.Outlined.Add" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Cancel</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="Submit">Save</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
133
Lantean.QBTMud/Components/Dialogs/FilterOptionsDialog.razor.cs
Normal file
133
Lantean.QBTMud/Components/Dialogs/FilterOptionsDialog.razor.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
using Lantean.QBTMud.Filter;
|
||||
using Lantean.QBTMud.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Lantean.QBTMud.Components.Dialogs
|
||||
{
|
||||
public partial class FilterOptionsDialog<T>
|
||||
{
|
||||
private static readonly IReadOnlyList<PropertyInfo> _properties = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);
|
||||
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
protected IReadOnlyList<PropertyInfo> Columns => _properties;
|
||||
|
||||
[Parameter]
|
||||
public List<PropertyFilterDefinition<T>>? FilterDefinitions { get; set; }
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
// as
|
||||
}
|
||||
|
||||
protected void RemoveDefinition(PropertyFilterDefinition<T> definition)
|
||||
{
|
||||
if (FilterDefinitions is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
FilterDefinitions.Remove(definition);
|
||||
}
|
||||
|
||||
protected void DefinitionOperatorChanged(PropertyFilterDefinition<T> definition, string @operator)
|
||||
{
|
||||
var existingDefinition = FilterDefinitions?.Find(d => d == definition);
|
||||
if (existingDefinition is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
existingDefinition.Operator = @operator;
|
||||
}
|
||||
|
||||
protected void DefinitionValueChanged(PropertyFilterDefinition<T> definition, object? value)
|
||||
{
|
||||
var existingDefinition = FilterDefinitions?.Find(d => d == definition);
|
||||
if (existingDefinition is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
existingDefinition.Value = value;
|
||||
}
|
||||
|
||||
protected string? Column { get; set; }
|
||||
protected Type? ColumnType { get; set; }
|
||||
protected string? Operator { get; set; }
|
||||
protected string? Value { get; set; }
|
||||
|
||||
protected void ColumnChanged(string column)
|
||||
{
|
||||
Column = column;
|
||||
ColumnType = _properties.FirstOrDefault(p => p.Name == column)?.PropertyType;
|
||||
}
|
||||
|
||||
protected IEnumerable<string> GetAvailablePropertyNames()
|
||||
{
|
||||
foreach (var propertyName in _properties.Select(p => p.Name))
|
||||
{
|
||||
if (!(FilterDefinitions?.Exists(d => d.Column == propertyName) ?? false))
|
||||
{
|
||||
yield return propertyName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void OperatorChanged(string @operator)
|
||||
{
|
||||
Operator = @operator;
|
||||
}
|
||||
|
||||
protected void ValueChanged(string value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
protected async Task AddDefinition()
|
||||
{
|
||||
if (Column is null || Operator is null || (FilterDefinitions?.Exists(d => d.Column == Column) ?? false))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CreateAndAdd(Column, Operator, Value);
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private void CreateAndAdd(string column, string @operator, object? value)
|
||||
{
|
||||
FilterDefinitions ??= [];
|
||||
FilterDefinitions.Add(new PropertyFilterDefinition<T>(column, @operator, value));
|
||||
|
||||
Column = null;
|
||||
Operator = null;
|
||||
Value = null;
|
||||
}
|
||||
|
||||
protected void Cancel()
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
|
||||
protected void Submit()
|
||||
{
|
||||
if (Column is not null && Operator is not null && !(FilterDefinitions?.Exists(d => d.Column == Column) ?? false))
|
||||
{
|
||||
CreateAndAdd(Column, Operator, Value);
|
||||
}
|
||||
|
||||
MudDialog.Close(DialogResult.Ok(FilterDefinitions));
|
||||
}
|
||||
|
||||
protected override Task Submit(KeyboardEvent keyboardEvent)
|
||||
{
|
||||
Submit();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudList T="string">
|
||||
<MudListItem Icon="@Icons.Material.Filled.Add" IconColor="Color.Info" OnClick="AddCategory">Add</MudListItem>
|
||||
<MudListItem Icon="@Icons.Material.Filled.Remove" IconColor="Color.Error" OnClick="RemoveCategory">Remove</MudListItem>
|
||||
<MudDivider />
|
||||
@foreach (var category in Categories)
|
||||
{
|
||||
var categoryRef = category;
|
||||
<MudListItem Icon="@GetIcon(categoryRef)" IconColor="Color.Default" OnClick="@(e => SetCategory(categoryRef))">@categoryRef</MudListItem>
|
||||
}
|
||||
</MudList>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
@@ -0,0 +1,138 @@
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBTMud.Helpers;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMud.Components.Dialogs
|
||||
{
|
||||
public partial class ManageCategoriesDialog
|
||||
{
|
||||
[Inject]
|
||||
protected IApiClient ApiClient { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected IDialogService DialogService { get; set; } = default!;
|
||||
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public IEnumerable<string> Hashes { get; set; } = [];
|
||||
|
||||
protected HashSet<string> Categories { get; set; } = [];
|
||||
|
||||
protected IList<string> TorrentCategories { get; private set; } = [];
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
Categories = [.. (await ApiClient.GetAllCategories()).Select(c => c.Key)];
|
||||
if (!Hashes.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await GetTorrentCategories();
|
||||
}
|
||||
|
||||
private async Task GetTorrentCategories()
|
||||
{
|
||||
var torrents = await ApiClient.GetTorrentList(hashes: Hashes.ToArray());
|
||||
TorrentCategories = torrents.Where(t => !string.IsNullOrEmpty(t.Category)).Select(t => t.Category!).ToList();
|
||||
}
|
||||
|
||||
protected string GetIcon(string tag)
|
||||
{
|
||||
var state = GetCategoryState(tag);
|
||||
return state switch
|
||||
{
|
||||
CategoryState.All => Icons.Material.Filled.RadioButtonChecked,
|
||||
CategoryState.Partial => CustomIcons.RadioIndeterminate,
|
||||
_ => Icons.Material.Filled.RadioButtonUnchecked
|
||||
};
|
||||
}
|
||||
|
||||
private enum CategoryState
|
||||
{
|
||||
All,
|
||||
Partial,
|
||||
None,
|
||||
}
|
||||
|
||||
private CategoryState GetCategoryState(string category)
|
||||
{
|
||||
if (category == string.Empty || TorrentCategories.Count == 0)
|
||||
{
|
||||
return CategoryState.None;
|
||||
}
|
||||
if (TorrentCategories.All(c => c == category))
|
||||
{
|
||||
return CategoryState.All;
|
||||
}
|
||||
else if (TorrentCategories.Any(c => c == category))
|
||||
{
|
||||
return CategoryState.Partial;
|
||||
}
|
||||
else
|
||||
{
|
||||
return CategoryState.None;
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task SetCategory(string category)
|
||||
{
|
||||
var state = GetCategoryState(category);
|
||||
|
||||
var nextState = state switch
|
||||
{
|
||||
CategoryState.All => CategoryState.None,
|
||||
CategoryState.Partial => CategoryState.All,
|
||||
CategoryState.None => CategoryState.All,
|
||||
_ => CategoryState.None,
|
||||
};
|
||||
|
||||
if (nextState == CategoryState.All)
|
||||
{
|
||||
await ApiClient.SetTorrentCategory(category, Hashes);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ApiClient.RemoveTorrentCategory(Hashes);
|
||||
}
|
||||
|
||||
await GetTorrentCategories();
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
protected async Task AddCategory()
|
||||
{
|
||||
var addedCategoy = await DialogService.InvokeAddCategoryDialog(ApiClient);
|
||||
if (addedCategoy is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await ApiClient.SetTorrentCategory(addedCategoy, Hashes);
|
||||
Categories.Add(addedCategoy);
|
||||
await GetTorrentCategories();
|
||||
}
|
||||
|
||||
protected async Task RemoveCategory()
|
||||
{
|
||||
await ApiClient.RemoveTorrentCategory(Hashes);
|
||||
await GetTorrentCategories();
|
||||
}
|
||||
|
||||
protected Task CloseDialog()
|
||||
{
|
||||
MudDialog.Close();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected void Cancel()
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
20
Lantean.QBTMud/Components/Dialogs/ManageTagsDialog.razor
Normal file
20
Lantean.QBTMud/Components/Dialogs/ManageTagsDialog.razor
Normal file
@@ -0,0 +1,20 @@
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudList T="string">
|
||||
<MudListItem Icon="@Icons.Material.Filled.Add" IconColor="Color.Info" OnClick="AddTag">Add</MudListItem>
|
||||
<MudListItem Icon="@Icons.Material.Filled.Remove" IconColor="Color.Error" OnClick="RemoveAllTags">Remove All</MudListItem>
|
||||
<MudDivider />
|
||||
@foreach (var tag in Tags)
|
||||
{
|
||||
var tagRef = tag;
|
||||
<MudListItem Icon="@GetIcon(tagRef)" IconColor="Color.Default" OnClick="@(e => SetTag(tagRef))">@tag</MudListItem>
|
||||
}
|
||||
</MudList>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
139
Lantean.QBTMud/Components/Dialogs/ManageTagsDialog.razor.cs
Normal file
139
Lantean.QBTMud/Components/Dialogs/ManageTagsDialog.razor.cs
Normal file
@@ -0,0 +1,139 @@
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBTMud.Helpers;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMud.Components.Dialogs
|
||||
{
|
||||
public partial class ManageTagsDialog
|
||||
{
|
||||
[Inject]
|
||||
protected IApiClient ApiClient { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected IDialogService DialogService { get; set; } = default!;
|
||||
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public IEnumerable<string> Hashes { get; set; } = [];
|
||||
|
||||
protected HashSet<string> Tags { get; set; } = [];
|
||||
|
||||
protected IList<IReadOnlyList<string>> TorrentTags { get; private set; } = [];
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
Tags = [.. (await ApiClient.GetAllTags())];
|
||||
if (!Hashes.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await GetTorrentTags();
|
||||
}
|
||||
|
||||
private async Task GetTorrentTags()
|
||||
{
|
||||
var torrents = await ApiClient.GetTorrentList(hashes: Hashes.ToArray());
|
||||
TorrentTags = torrents.Select(t => t.Tags ?? []).ToList();
|
||||
}
|
||||
|
||||
protected string GetIcon(string tag)
|
||||
{
|
||||
var state = GetTagState(tag);
|
||||
return state switch
|
||||
{
|
||||
TagState.All => Icons.Material.Filled.CheckBox,
|
||||
TagState.Partial => Icons.Material.Filled.IndeterminateCheckBox,
|
||||
_ => Icons.Material.Filled.CheckBoxOutlineBlank
|
||||
};
|
||||
}
|
||||
|
||||
private enum TagState
|
||||
{
|
||||
All,
|
||||
Partial,
|
||||
None,
|
||||
}
|
||||
|
||||
private TagState GetTagState(string tag)
|
||||
{
|
||||
if (TorrentTags.All(t => t.Contains(tag)))
|
||||
{
|
||||
return TagState.All;
|
||||
}
|
||||
else if (TorrentTags.Any(t => t.Contains(tag)))
|
||||
{
|
||||
return TagState.Partial;
|
||||
}
|
||||
else
|
||||
{
|
||||
return TagState.None;
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task SetTag(string tag)
|
||||
{
|
||||
var state = GetTagState(tag);
|
||||
|
||||
var nextState = state switch
|
||||
{
|
||||
TagState.All => TagState.None,
|
||||
TagState.Partial => TagState.All,
|
||||
TagState.None => TagState.All,
|
||||
_ => TagState.None,
|
||||
};
|
||||
|
||||
if (nextState == TagState.All)
|
||||
{
|
||||
await ApiClient.AddTorrentTag(tag, Hashes);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ApiClient.RemoveTorrentTag(tag, Hashes);
|
||||
}
|
||||
|
||||
await GetTorrentTags();
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
protected async Task AddTag()
|
||||
{
|
||||
var addedTags = await DialogService.ShowAddTagsDialog();
|
||||
|
||||
if (addedTags is null || addedTags.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await ApiClient.AddTorrentTags(addedTags, Hashes);
|
||||
|
||||
foreach (var tag in addedTags)
|
||||
{
|
||||
Tags.Add(tag);
|
||||
}
|
||||
await GetTorrentTags();
|
||||
}
|
||||
|
||||
protected async Task RemoveAllTags()
|
||||
{
|
||||
await ApiClient.RemoveTorrentTags(Tags, Hashes);
|
||||
await GetTorrentTags();
|
||||
}
|
||||
|
||||
protected Task CloseDialog()
|
||||
{
|
||||
MudDialog.Close();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected void Cancel()
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
26
Lantean.QBTMud/Components/Dialogs/MultipleFieldDialog.razor
Normal file
26
Lantean.QBTMud/Components/Dialogs/MultipleFieldDialog.razor
Normal file
@@ -0,0 +1,26 @@
|
||||
@inherits SubmittableDialog
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<table width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width: 100%"><MudTextField T="string" Label="@(Label)" Value="@Value" ValueChanged="SetValue" Required Variant="Variant.Outlined" /></td>
|
||||
<td><MudIconButton Icon="@Icons.Material.Filled.Add" OnClick="AddValue" /></td>
|
||||
</tr>
|
||||
@foreach (var value in NewValues)
|
||||
{
|
||||
var valueRef = value;
|
||||
<tr>
|
||||
<td>@value</td>
|
||||
<td><MudIconButton Icon="@Icons.Material.Filled.Delete" OnClick="@(e => DeleteValue(valueRef))" /></td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Cancel</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="Submit">Save</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
@@ -0,0 +1,70 @@
|
||||
using Lantean.QBTMud.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMud.Components.Dialogs
|
||||
{
|
||||
public partial class MultipleFieldDialog
|
||||
{
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public string Label { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public HashSet<string> Values { get; set; } = [];
|
||||
|
||||
protected HashSet<string> NewValues { get; } = [];
|
||||
|
||||
protected string? Value { get; set; }
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
if (NewValues.Count == 0)
|
||||
{
|
||||
foreach (var value in Values)
|
||||
{
|
||||
NewValues.Add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void AddValue()
|
||||
{
|
||||
if (string.IsNullOrEmpty(Value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
NewValues.Add(Value);
|
||||
Value = null;
|
||||
}
|
||||
|
||||
protected void SetValue(string tracker)
|
||||
{
|
||||
Value = tracker;
|
||||
}
|
||||
|
||||
protected void DeleteValue(string tracker)
|
||||
{
|
||||
NewValues.Remove(tracker);
|
||||
}
|
||||
|
||||
protected void Cancel()
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
|
||||
protected void Submit()
|
||||
{
|
||||
MudDialog.Close(NewValues);
|
||||
}
|
||||
|
||||
protected override Task Submit(KeyboardEvent keyboardEvent)
|
||||
{
|
||||
Submit();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
16
Lantean.QBTMud/Components/Dialogs/NumericFieldDialog.razor
Normal file
16
Lantean.QBTMud/Components/Dialogs/NumericFieldDialog.razor
Normal file
@@ -0,0 +1,16 @@
|
||||
@typeparam T
|
||||
@inherits SubmittableDialog
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="string" Label="@Label" Value="@GetDisplayValue()" ValueChanged="ValueChanged" Min="@(Min.ToString())" Max="@(Max.ToString())" Disabled="Disabled" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Cancel</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="Submit">Save</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
@@ -0,0 +1,76 @@
|
||||
using Lantean.QBTMud.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Lantean.QBTMud.Components.Dialogs
|
||||
{
|
||||
public partial class NumericFieldDialog<T> where T : struct, INumber<T>
|
||||
{
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public string? Label { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public T Value { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public T Min { get; set; } = T.Zero;
|
||||
|
||||
[Parameter]
|
||||
public T Max { get; set; } = T.One;
|
||||
|
||||
[Parameter]
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<T, string>? ValueDisplayFunc { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<string, T>? ValueGetFunc { get; set; }
|
||||
|
||||
private string? GetDisplayValue()
|
||||
{
|
||||
var value = ValueDisplayFunc?.Invoke(Value);
|
||||
return value is null ? Value.ToString() : value;
|
||||
}
|
||||
|
||||
protected void ValueChanged(string value)
|
||||
{
|
||||
if (ValueGetFunc is not null)
|
||||
{
|
||||
Value = ValueGetFunc.Invoke(value);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (T.TryParse(value, null, out var result))
|
||||
{
|
||||
Value = result;
|
||||
}
|
||||
else
|
||||
{
|
||||
Value = Min;
|
||||
}
|
||||
}
|
||||
|
||||
protected void Cancel()
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
|
||||
protected void Submit()
|
||||
{
|
||||
MudDialog.Close(DialogResult.Ok(Value));
|
||||
}
|
||||
|
||||
protected override Task Submit(KeyboardEvent keyboardEvent)
|
||||
{
|
||||
Submit();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
42
Lantean.QBTMud/Components/Dialogs/RenameFilesDialog.razor
Normal file
42
Lantean.QBTMud/Components/Dialogs/RenameFilesDialog.razor
Normal file
@@ -0,0 +1,42 @@
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<DynamicTable T="FileRow"
|
||||
ColumnDefinitions="Columns"
|
||||
Items="Files"
|
||||
MultiSelection="true"
|
||||
SelectedItems="SelectedItems"
|
||||
SelectedItemsChanged="SelectedItemsChanged"
|
||||
PreSorted="true"
|
||||
SortColumnChanged="SortColumnChanged"
|
||||
SortDirectionChanged="SortDirectionChanged"
|
||||
Class="file-list" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Close</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="Submit">Rename</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@code {
|
||||
private static RenderFragment<RowContext<FileRow>> NameColumn
|
||||
{
|
||||
get
|
||||
{
|
||||
return context => __builder =>
|
||||
{
|
||||
<div style="@($"margin-left: {(context.Data.Level * 14) + (context.Data.Level >= 1 ? 16 : 0)}px")">
|
||||
@if (context.Data.IsFolder)
|
||||
{
|
||||
<MudIcon Icon="@Icons.Material.Filled.Folder" Class="pt-0" Style="margin-right: 4px; position: relative; top: 7px; margin-left: -15px" />
|
||||
}
|
||||
@context.Data.OriginalName
|
||||
</div>
|
||||
;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
468
Lantean.QBTMud/Components/Dialogs/RenameFilesDialog.razor.cs
Normal file
468
Lantean.QBTMud/Components/Dialogs/RenameFilesDialog.razor.cs
Normal file
@@ -0,0 +1,468 @@
|
||||
using Blazored.LocalStorage;
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBTMud.Helpers;
|
||||
using Lantean.QBTMud.Models;
|
||||
using Lantean.QBTMud.Services;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Lantean.QBTMud.Components.Dialogs
|
||||
{
|
||||
public partial class RenameFilesDialog
|
||||
{
|
||||
private const string _preferencesStorageKey = "RenameFilesDialog.MultiRenamePreferences";
|
||||
|
||||
protected static readonly Dictionary<AppliesTo, string> AppliesToItems = Enum.GetValues<AppliesTo>().ToDictionary(v => v, v => v.GetDescriptionAttributeOrDefault());
|
||||
|
||||
private readonly Dictionary<string, RenderFragment<RowContext<FileRow>>> _columnRenderFragments = [];
|
||||
|
||||
private string? _sortColumn;
|
||||
private SortDirection _sortDirection;
|
||||
|
||||
[Inject]
|
||||
protected IApiClient ApiClient { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected IDataManager DataManager { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected ILocalStorageService LocalStorage { get; set; } = default!;
|
||||
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public string? Hash { get; set; }
|
||||
|
||||
protected HashSet<FileRow> SelectedItems { get; set; } = [];
|
||||
|
||||
protected IEnumerable<FileRow> FileList { get; private set; } = [];
|
||||
|
||||
protected IEnumerable<FileRow> Files => GetFiles();
|
||||
|
||||
protected HashSet<string> ExpandedNodes { get; set; } = [];
|
||||
|
||||
public RenameFilesDialog()
|
||||
{
|
||||
_columnRenderFragments.Add("Name", NameColumn);
|
||||
//_columnRenderFragments.Add("Replacement", ReplacementColumn);
|
||||
}
|
||||
|
||||
private ReadOnlyCollection<FileRow> GetFiles()
|
||||
{
|
||||
var maxLevel = FileList.Max(f => f.Level);
|
||||
// this is a flat file structure
|
||||
if (maxLevel == 0)
|
||||
{
|
||||
return FileList.OrderByDirection(_sortDirection, GetSortSelector()).ToList().AsReadOnly();
|
||||
}
|
||||
|
||||
var list = new List<FileRow>();
|
||||
|
||||
var rootItems = FileList.Where(c => c.Level == 0).OrderByDirection(_sortDirection, GetSortSelector()).ToList();
|
||||
foreach (var item in rootItems)
|
||||
{
|
||||
list.Add(item);
|
||||
|
||||
if (item.IsFolder)
|
||||
{
|
||||
var level = 0;
|
||||
var descendants = GetChildren(item, level);
|
||||
foreach (var descendant in descendants)
|
||||
{
|
||||
list.Add(descendant);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list.AsReadOnly();
|
||||
}
|
||||
|
||||
private IEnumerable<FileRow> GetRenamedItems(IEnumerable<ContentItem> items)
|
||||
{
|
||||
var renamedFiles = FileNameMatcher.GetRenamedFiles(
|
||||
SelectedItems,
|
||||
Search,
|
||||
UseRegex,
|
||||
Replacement,
|
||||
MatchAllOccurrences,
|
||||
CaseSensitive,
|
||||
AppliesToValue,
|
||||
IncludeFiles,
|
||||
IncludeFolders,
|
||||
ReplaceAll,
|
||||
FileEnumerationStart);
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
var fileRow = CreateFileRow(item);
|
||||
if (renamedFiles.TryGetValue(fileRow.Name, out var renamedRow))
|
||||
{
|
||||
yield return renamedRow;
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return fileRow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static FileRow CreateFileRow(ContentItem item)
|
||||
{
|
||||
var fileRow = new FileRow
|
||||
{
|
||||
IsFolder = item.IsFolder,
|
||||
Level = item.Level,
|
||||
NewName = item.DisplayName,
|
||||
OriginalName = item.DisplayName,
|
||||
Name = item.Name,
|
||||
Path = item.Path,
|
||||
};
|
||||
|
||||
return fileRow;
|
||||
}
|
||||
|
||||
//private IEnumerable<(FileRow, string)> GetRenamedItemsOld(IEnumerable<FileRow> items)
|
||||
//{
|
||||
// foreach (var item in items)
|
||||
// {
|
||||
// if (!SelectedItems.Contains(item))
|
||||
// {
|
||||
// yield return (item, "");
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// if ((item.IsFolder && !IncludeFolders) || (!item.IsFolder && !IncludeFiles))
|
||||
// {
|
||||
// yield return (item, "");
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// if (string.IsNullOrEmpty(Search) || string.IsNullOrEmpty(Replacement))
|
||||
// {
|
||||
// yield return (item, "");
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// var newName = item.DisplayName;
|
||||
// switch (AppliesToValue)
|
||||
// {
|
||||
// case AppliesTo.FilenameExtension:
|
||||
// newName = ReplaceInString(item.DisplayName);
|
||||
// break;
|
||||
|
||||
// case AppliesTo.Filename:
|
||||
// var extension = Path.GetExtension(item.DisplayName);
|
||||
// var filename = Path.GetFileNameWithoutExtension(item.DisplayName);
|
||||
// filename = ReplaceInString(filename);
|
||||
// newName = filename + extension;
|
||||
// break;
|
||||
|
||||
// case AppliesTo.Extension:
|
||||
// extension = Path.GetExtension(item.DisplayName);
|
||||
// string newExtension = ReplaceInString(extension);
|
||||
// newName = Path.GetFileNameWithoutExtension(item.DisplayName) + newExtension;
|
||||
// break;
|
||||
// }
|
||||
|
||||
// yield return (item, newName);
|
||||
// }
|
||||
//}
|
||||
|
||||
//private string ReplaceInString(string input)
|
||||
//{
|
||||
// if (UseRegex)
|
||||
// {
|
||||
// var regex = new Regex(Search, CaseSensitive ? RegexOptions.None : RegexOptions.IgnoreCase);
|
||||
// return MatchAllOccurrences
|
||||
// ? regex.Replace(input, Replacement)
|
||||
// : regex.Replace(input, Replacement, 1);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// var comparison = CaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
|
||||
|
||||
// if (MatchAllOccurrences)
|
||||
// {
|
||||
// return input.Replace(Search, Replacement, comparison);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// var index = input.IndexOf(Search, comparison);
|
||||
// if (index == -1)
|
||||
// {
|
||||
// return input;
|
||||
// }
|
||||
// return input.Remove(index, Search.Length).Insert(index, Replacement);
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
protected async Task DoRename()
|
||||
{
|
||||
if (Hash is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await RenameAsync(Hash, Files.Where(f => f.Renamed).ToList());
|
||||
}
|
||||
|
||||
private async Task RenameAsync(string hash, List<FileRow> matchedFiles)
|
||||
{
|
||||
if (matchedFiles == null || matchedFiles.Count == 0 || string.IsNullOrEmpty(hash))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = matchedFiles.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var file = matchedFiles[i];
|
||||
var (success, errorMessage) = await RenameItem(hash, file);
|
||||
file.Renamed = success;
|
||||
file.ErrorMessage = errorMessage;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<(bool, string?)> RenameItem(string hash, FileRow match)
|
||||
{
|
||||
if (match.NewName == match.OriginalName)
|
||||
{
|
||||
// Original file name is identical to Renamed
|
||||
return (false, null);
|
||||
}
|
||||
|
||||
var newName = match.NewName!;
|
||||
|
||||
var parentPath = Path.GetDirectoryName(match.Name);
|
||||
var oldPath = string.IsNullOrEmpty(parentPath)
|
||||
? match.OriginalName
|
||||
: Path.Combine(parentPath, match.OriginalName);
|
||||
var newPath = string.IsNullOrEmpty(parentPath)
|
||||
? newName
|
||||
: Path.Combine(parentPath, newName);
|
||||
|
||||
try
|
||||
{
|
||||
if (match.IsFolder)
|
||||
{
|
||||
await ApiClient.RenameFile(hash, oldPath, newPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ApiClient.RenameFile(hash, oldPath, newPath);
|
||||
}
|
||||
return (true, null);
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
return (false, ex.Message != "" ? ex.Message : $"Error with request: {ex.StatusCode}.");
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<FileRow> GetChildren(FileRow folder, int level)
|
||||
{
|
||||
level++;
|
||||
var descendantsKey = folder.Name.GetDescendantsKey(level);
|
||||
|
||||
foreach (var item in FileList.Where(f => f.Name.StartsWith(descendantsKey) && f.Level == level).OrderByDirection(_sortDirection, GetSortSelector()))
|
||||
{
|
||||
if (item.IsFolder)
|
||||
{
|
||||
var descendants = GetChildren(item, level);
|
||||
// if the filter returns some results then show folder item
|
||||
if (descendants.Any())
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
|
||||
// then show children
|
||||
foreach (var descendant in descendants)
|
||||
{
|
||||
yield return descendant;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Func<FileRow, object?> GetSortSelector()
|
||||
{
|
||||
var sortSelector = ColumnsDefinitions.Find(c => c.Id == _sortColumn)?.SortSelector;
|
||||
|
||||
return sortSelector ?? (i => i.Name);
|
||||
}
|
||||
|
||||
protected void SortColumnChanged(string sortColumn)
|
||||
{
|
||||
_sortColumn = sortColumn;
|
||||
}
|
||||
|
||||
protected void SortDirectionChanged(SortDirection sortDirection)
|
||||
{
|
||||
_sortDirection = sortDirection;
|
||||
}
|
||||
|
||||
protected void SelectedItemsChanged(HashSet<FileRow> selectedItems)
|
||||
{
|
||||
SelectedItems = selectedItems;
|
||||
}
|
||||
|
||||
protected string Search { get; set; } = "";
|
||||
|
||||
protected void SearchChanged(string value)
|
||||
{
|
||||
Search = value;
|
||||
}
|
||||
|
||||
protected bool UseRegex { get; set; }
|
||||
|
||||
protected void UseRegexChanged(bool value)
|
||||
{
|
||||
UseRegex = value;
|
||||
}
|
||||
|
||||
protected bool MatchAllOccurrences { get; set; }
|
||||
|
||||
protected void MatchAllOccurrencesChanged(bool value)
|
||||
{
|
||||
MatchAllOccurrences = value;
|
||||
}
|
||||
|
||||
protected bool CaseSensitive { get; set; }
|
||||
|
||||
protected void CaseSensitiveChanged(bool value)
|
||||
{
|
||||
CaseSensitive = value;
|
||||
}
|
||||
|
||||
protected string Replacement { get; set; } = "";
|
||||
|
||||
protected void ReplacementChanged(string value)
|
||||
{
|
||||
Replacement = value;
|
||||
}
|
||||
|
||||
protected AppliesTo AppliesToValue { get; set; } = AppliesTo.FilenameExtension;
|
||||
|
||||
protected void AppliesToChanged(AppliesTo value)
|
||||
{
|
||||
AppliesToValue = value;
|
||||
}
|
||||
|
||||
protected bool IncludeFiles { get; set; } = true;
|
||||
|
||||
protected void IncludeFilesChanged(bool value)
|
||||
{
|
||||
IncludeFiles = value;
|
||||
}
|
||||
|
||||
protected bool IncludeFolders { get; set; }
|
||||
|
||||
protected void IncludeFoldersChanged(bool value)
|
||||
{
|
||||
IncludeFolders = value;
|
||||
}
|
||||
|
||||
protected int FileEnumerationStart { get; set; }
|
||||
|
||||
protected void FileEnumerationStartChanged(int value)
|
||||
{
|
||||
FileEnumerationStart = value;
|
||||
}
|
||||
|
||||
protected bool ReplaceAll { get; set; }
|
||||
|
||||
protected void ReplaceAllChanged(bool value)
|
||||
{
|
||||
ReplaceAll = value;
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var preferences = await LocalStorage.GetItemAsync<MultiRenamePreferences>(_preferencesStorageKey) ?? new();
|
||||
|
||||
if (preferences.RememberPreferences)
|
||||
{
|
||||
Search = preferences.Search;
|
||||
UseRegex = preferences.UseRegex;
|
||||
MatchAllOccurrences = preferences.MatchAllOccurrences;
|
||||
CaseSensitive = preferences.CaseSensitive;
|
||||
Replacement = preferences.Replace;
|
||||
AppliesToValue = preferences.AppliesTo;
|
||||
IncludeFiles = preferences.IncludeFiles;
|
||||
IncludeFolders = preferences.IncludeFolders;
|
||||
FileEnumerationStart = preferences.FileEnumerationStart;
|
||||
ReplaceAll = preferences.ReplaceAll;
|
||||
}
|
||||
|
||||
if (Hash is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var contents = await ApiClient.GetTorrentContents(Hash);
|
||||
FileList = GetRenamedItems(DataManager.CreateContentsList(contents).Values);
|
||||
}
|
||||
|
||||
protected void Cancel()
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
|
||||
protected void Submit()
|
||||
{
|
||||
MudDialog.Close();
|
||||
}
|
||||
|
||||
protected IEnumerable<ColumnDefinition<FileRow>> Columns => GetColumnDefinitions();
|
||||
|
||||
private IEnumerable<ColumnDefinition<FileRow>> GetColumnDefinitions()
|
||||
{
|
||||
foreach (var columnDefinition in ColumnsDefinitions)
|
||||
{
|
||||
if (_columnRenderFragments.TryGetValue(columnDefinition.Header, out var fragment))
|
||||
{
|
||||
columnDefinition.RowTemplate = fragment;
|
||||
}
|
||||
|
||||
yield return columnDefinition;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<ColumnDefinition<FileRow>> ColumnsDefinitions { get; } =
|
||||
[
|
||||
ColumnDefinitionHelper.CreateColumnDefinition("Name", c => c.Name, NameColumn, width: 400, initialDirection: SortDirection.Ascending, classFunc: c => c.IsFolder ? "px-0 pt-0 pb-2" : "pa-2"),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<FileRow>("Replacement", c => c.NewName),
|
||||
];
|
||||
|
||||
private sealed class MultiRenamePreferences
|
||||
{
|
||||
public bool RememberPreferences { get; set; } = false;
|
||||
|
||||
public string Search { get; set; } = "";
|
||||
|
||||
public bool UseRegex { get; set; } = false;
|
||||
|
||||
public bool MatchAllOccurrences { get; set; } = false;
|
||||
|
||||
public bool CaseSensitive { get; set; } = false;
|
||||
|
||||
public string Replace { get; set; } = "";
|
||||
|
||||
public AppliesTo AppliesTo { get; set; } = AppliesTo.FilenameExtension;
|
||||
|
||||
public bool IncludeFiles { get; set; } = true;
|
||||
|
||||
public bool IncludeFolders { get; set; } = false;
|
||||
|
||||
public int FileEnumerationStart { get; set; } = 0;
|
||||
|
||||
public bool ReplaceAll { get; set; } = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
106
Lantean.QBTMud/Components/Dialogs/RssRulesDialog.razor
Normal file
106
Lantean.QBTMud/Components/Dialogs/RssRulesDialog.razor
Normal file
@@ -0,0 +1,106 @@
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudGrid>
|
||||
<MudItem xs="3">
|
||||
<MudToolBar Class="px-0" Dense>
|
||||
<MudText Class="no-wrap">Download Rules</MudText>
|
||||
<MudSpacer />
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Add" OnClick="AddRule" />
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Delete" OnClick="RemoveRule" Disabled="SelectedRuleName is null" />
|
||||
</MudToolBar>
|
||||
|
||||
<MudList T="string" SelectionMode="SelectionMode.SingleSelection" SelectedValue="SelectedRuleName" SelectedValueChanged="SelectedRuleChanged">
|
||||
@foreach (var (ruleName, rule) in Rules)
|
||||
{
|
||||
<MudListItem Icon="@((rule?.Enabled ?? true) ? Icons.Material.Filled.CheckBox : Icons.Material.Filled.CheckBoxOutlineBlank)" Text="@ruleName" Value="@ruleName" />
|
||||
}
|
||||
</MudList>
|
||||
</MudItem>
|
||||
<MudItem xs="6">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Use regular expressions" Value="UseRegex" ValueChanged="UseRegexChanged" Disabled="@(SelectedRuleName is null)" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="Must contain" Value="MustContain" ValueChanged="MustContainChanged" Disabled="@(SelectedRuleName is null)" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="Must not contain" Value="MustNotContain" ValueChanged="MustNotContainChanged" Disabled="@(SelectedRuleName is null)" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="Episode filter" Value="EpisodeFilter" ValueChanged="EpisodeFilterChanged" Disabled="@(SelectedRuleName is null)" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Use smart episode filter" Value="SmartFilter" ValueChanged="SmartFilterChanged" Disabled="@(SelectedRuleName is null)" />
|
||||
</MudItem>
|
||||
<MudDivider />
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="string" Label="Assign category" Value="Category" ValueChanged="CategoryChanged" Disabled="@(SelectedRuleName is null)" Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="@("")"></MudSelectItem>
|
||||
@foreach (var category in Categories)
|
||||
{
|
||||
<MudSelectItem Value="@category">@category</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="Add tags" Value="Tags" ValueChanged="TagsChanged" Disabled="@(SelectedRuleName is null)" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Save to a different directory" Value="SaveToDifferentDirectory" ValueChanged="SaveToDifferentDirectoryChanged" Disabled="@(SelectedRuleName is null)" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Ignore Subsequent Matches for (0 to Disable)" Value="IgnoreDays" ValueChanged="IgnoreDaysChanged" Disabled="@(SelectedRuleName is null)" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="string" Label="Add paused" Value="AddPaused" ValueChanged="AddPausedChanged" Disabled="@(SelectedRuleName is null)" Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="@("default")">Use global settings</MudSelectItem>
|
||||
<MudSelectItem Value="@("always")">Always</MudSelectItem>
|
||||
<MudSelectItem Value="@("never")">Never</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="string" Label="Torrent content layout" Value="ContentLayout" ValueChanged="ContentLayoutChanged" Disabled="@(SelectedRuleName is null)" Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="@("Default")">Use global settings</MudSelectItem>
|
||||
<MudSelectItem Value="@("Original")">Original</MudSelectItem>
|
||||
<MudSelectItem Value="@("Subfolder")">Create subfolder</MudSelectItem>
|
||||
<MudSelectItem Value="@("NoSubfolder")">Don't create subfolder</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudList T="string" SelectionMode="SelectionMode.MultiSelection" SelectedValues="SelectedFeeds" SelectedValuesChanged="SelectedFeedsChanged" CheckBoxColor="Color.Default" Disabled="@(SelectedRuleName is null)" Dense>
|
||||
<MudListSubheader>Apply Rule to Feeds</MudListSubheader>
|
||||
@foreach (var (feed, _) in Feeds)
|
||||
{
|
||||
<MudListItem Value="@feed" Text="@feed" />
|
||||
}
|
||||
</MudList>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudItem>
|
||||
<MudItem xs="3">
|
||||
<MudText Class="no-wrap">Matching RSS Articles</MudText>
|
||||
<MudList T="string" ReadOnly Dense>
|
||||
@if (MatchingArticles is not null)
|
||||
{
|
||||
foreach (var (feed, articles) in MatchingArticles)
|
||||
{
|
||||
<MudListItem Text="@feed" Expanded="true">
|
||||
<NestedList>
|
||||
@foreach (var article in articles)
|
||||
{
|
||||
<MudListItem Text="@article" />
|
||||
}
|
||||
</NestedList>
|
||||
</MudListItem>
|
||||
}
|
||||
}
|
||||
</MudList>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Close</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="Submit">Save</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
340
Lantean.QBTMud/Components/Dialogs/RssRulesDialog.razor.cs
Normal file
340
Lantean.QBTMud/Components/Dialogs/RssRulesDialog.razor.cs
Normal file
@@ -0,0 +1,340 @@
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBTMud.Helpers;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMud.Components.Dialogs
|
||||
{
|
||||
public partial class RssRulesDialog
|
||||
{
|
||||
private readonly List<string> _unsavedRuleNames = [];
|
||||
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected IDialogService DialogService { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected IApiClient ApiClient { get; set; } = default!;
|
||||
|
||||
protected string? SelectedRuleName { get; set; }
|
||||
|
||||
protected Dictionary<string, QBitTorrentClient.Models.AutoDownloadingRule?> Rules { get; set; } = [];
|
||||
|
||||
protected IEnumerable<string> Categories { get; set; } = [];
|
||||
|
||||
protected Dictionary<string, string> Feeds { get; set; } = [];
|
||||
|
||||
protected IReadOnlyDictionary<string, IReadOnlyList<string>>? MatchingArticles { get; set; }
|
||||
|
||||
private QBitTorrentClient.Models.AutoDownloadingRule SelectedRule { get; set; } = default!;
|
||||
|
||||
protected bool UseRegex { get; set; }
|
||||
|
||||
protected void UseRegexChanged(bool value)
|
||||
{
|
||||
UseRegex = value;
|
||||
SelectedRule.UseRegex = value;
|
||||
}
|
||||
|
||||
protected string? MustContain { get; set; }
|
||||
|
||||
protected void MustContainChanged(string value)
|
||||
{
|
||||
MustContain = value;
|
||||
SelectedRule.MustContain = value;
|
||||
}
|
||||
|
||||
protected string? MustNotContain { get; set; }
|
||||
|
||||
protected void MustNotContainChanged(string value)
|
||||
{
|
||||
MustNotContain = value;
|
||||
SelectedRule.MustNotContain = value;
|
||||
}
|
||||
|
||||
protected string? EpisodeFilter { get; set; }
|
||||
|
||||
protected void EpisodeFilterChanged(string value)
|
||||
{
|
||||
EpisodeFilter = value;
|
||||
SelectedRule.EpisodeFilter = value;
|
||||
}
|
||||
|
||||
protected bool SmartFilter { get; set; }
|
||||
|
||||
protected void SmartFilterChanged(bool value)
|
||||
{
|
||||
SmartFilter = value;
|
||||
SelectedRule.SmartFilter = value;
|
||||
}
|
||||
|
||||
protected string? Category { get; set; }
|
||||
|
||||
protected void CategoryChanged(string value)
|
||||
{
|
||||
Category = value;
|
||||
SelectedRule.TorrentParams.Category = value;
|
||||
}
|
||||
|
||||
protected string? Tags { get; set; }
|
||||
|
||||
protected void TagsChanged(string value)
|
||||
{
|
||||
Tags = value;
|
||||
SelectedRule.TorrentParams.Tags = value.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
||||
protected bool SaveToDifferentDirectory { get; set; }
|
||||
|
||||
protected void SaveToDifferentDirectoryChanged(bool value)
|
||||
{
|
||||
SaveToDifferentDirectory = value;
|
||||
if (!value)
|
||||
{
|
||||
SelectedRule.TorrentParams.SavePath = "";
|
||||
}
|
||||
}
|
||||
|
||||
protected string? SaveTo { get; set; }
|
||||
|
||||
protected void SaveToChanged(string value)
|
||||
{
|
||||
SaveTo = value;
|
||||
SelectedRule.TorrentParams.SavePath = value;
|
||||
SelectedRule.TorrentParams.UseAutoTmm = false;
|
||||
}
|
||||
|
||||
protected int IgnoreDays { get; set; }
|
||||
|
||||
protected void IgnoreDaysChanged(int value)
|
||||
{
|
||||
IgnoreDays = value;
|
||||
SelectedRule.IgnoreDays = value;
|
||||
}
|
||||
|
||||
protected string? AddPaused { get; set; }
|
||||
|
||||
protected void AddPausedChanged(string value)
|
||||
{
|
||||
AddPaused = value;
|
||||
switch (value)
|
||||
{
|
||||
case "default":
|
||||
SelectedRule.TorrentParams.Stopped = null;
|
||||
break;
|
||||
|
||||
case "always":
|
||||
SelectedRule.TorrentParams.Stopped = true;
|
||||
break;
|
||||
|
||||
case "never":
|
||||
SelectedRule.TorrentParams.Stopped = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected string? ContentLayout { get; set; }
|
||||
|
||||
protected void ContentLayoutChanged(string value)
|
||||
{
|
||||
ContentLayout = value;
|
||||
|
||||
switch (value)
|
||||
{
|
||||
case "Default":
|
||||
SelectedRule.TorrentParams.ContentLayout = null;
|
||||
break;
|
||||
|
||||
case "Original":
|
||||
SelectedRule.TorrentParams.ContentLayout = "Original";
|
||||
break;
|
||||
|
||||
case "Subfolder":
|
||||
SelectedRule.TorrentParams.ContentLayout = "Subfolder";
|
||||
break;
|
||||
|
||||
case "NoSubfolder":
|
||||
SelectedRule.TorrentParams.ContentLayout = "NoSubfolder";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected IReadOnlyCollection<string>? SelectedFeeds { get; set; }
|
||||
|
||||
protected void SelectedFeedsChanged(IReadOnlyCollection<string> value)
|
||||
{
|
||||
SelectedFeeds = value;
|
||||
|
||||
var feeds = new List<string>();
|
||||
foreach (var feed in SelectedFeeds)
|
||||
{
|
||||
if (Feeds.TryGetValue(feed, out var url))
|
||||
{
|
||||
feeds.Add(url);
|
||||
}
|
||||
}
|
||||
|
||||
SelectedRule.AffectedFeeds = feeds;
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var rules = await ApiClient.GetAllRssAutoDownloadingRules();
|
||||
foreach (var kvp in rules)
|
||||
{
|
||||
Rules.Add(kvp.Key, kvp.Value);
|
||||
}
|
||||
|
||||
Categories = (await ApiClient.GetAllCategories()).Keys;
|
||||
|
||||
Feeds = (await ApiClient.GetAllRssItems(false)).ToDictionary(f => f.Key, f => f.Value.Url);
|
||||
}
|
||||
|
||||
protected async Task AddRule()
|
||||
{
|
||||
var ruleName = await DialogService.ShowStringFieldDialog("Add Rule", "Name", null);
|
||||
if (ruleName is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Rules.ContainsKey(ruleName))
|
||||
{
|
||||
SelectedRuleName = ruleName;
|
||||
return;
|
||||
}
|
||||
|
||||
Rules.Add(ruleName, null);
|
||||
_unsavedRuleNames.Add(ruleName);
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
protected async Task RemoveRule()
|
||||
{
|
||||
if (SelectedRuleName is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_unsavedRuleNames.Contains(SelectedRuleName))
|
||||
{
|
||||
_unsavedRuleNames.Remove(SelectedRuleName);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ApiClient.RemoveRssAutoDownloadingRule(SelectedRuleName);
|
||||
}
|
||||
|
||||
Rules.Remove(SelectedRuleName);
|
||||
SelectedRuleName = null;
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
protected async Task SelectedRuleChanged(string value)
|
||||
{
|
||||
SelectedRuleName = value;
|
||||
|
||||
if (!Rules.TryGetValue(SelectedRuleName, out var rule))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_unsavedRuleNames.Contains(SelectedRuleName))
|
||||
{
|
||||
MatchingArticles = await ApiClient.GetRssMatchingArticles(SelectedRuleName);
|
||||
}
|
||||
else
|
||||
{
|
||||
MatchingArticles = null;
|
||||
}
|
||||
|
||||
if (rule is null)
|
||||
{
|
||||
rule = new QBitTorrentClient.Models.AutoDownloadingRule();
|
||||
|
||||
Rules[SelectedRuleName] = rule;
|
||||
}
|
||||
SelectedRule = rule;
|
||||
|
||||
UseRegex = SelectedRule.UseRegex ?? false;
|
||||
MustContain = SelectedRule.MustContain;
|
||||
MustNotContain = SelectedRule.MustNotContain;
|
||||
EpisodeFilter = SelectedRule.EpisodeFilter;
|
||||
SmartFilter = SelectedRule.SmartFilter ?? false;
|
||||
Category = SelectedRule.TorrentParams.Category;
|
||||
Tags = string.Join(' ', SelectedRule.TorrentParams.Tags);
|
||||
SaveToDifferentDirectory = !string.IsNullOrEmpty(SelectedRule.TorrentParams.SavePath);
|
||||
SaveTo = SelectedRule.TorrentParams.SavePath;
|
||||
IgnoreDays = SelectedRule.IgnoreDays ?? 0;
|
||||
switch (SelectedRule.TorrentParams.Stopped)
|
||||
{
|
||||
case null:
|
||||
AddPaused = "default";
|
||||
break;
|
||||
|
||||
case true:
|
||||
AddPaused = "always";
|
||||
break;
|
||||
|
||||
case false:
|
||||
AddPaused = "never";
|
||||
break;
|
||||
}
|
||||
|
||||
switch (SelectedRule.TorrentParams.ContentLayout)
|
||||
{
|
||||
case "Default":
|
||||
ContentLayout = null;
|
||||
break;
|
||||
|
||||
case "Original":
|
||||
ContentLayout = "Original";
|
||||
break;
|
||||
|
||||
case "Subfolder":
|
||||
ContentLayout = "Subfolder";
|
||||
break;
|
||||
|
||||
case "NoSubfolder":
|
||||
ContentLayout = "NoSubfolder";
|
||||
break;
|
||||
}
|
||||
|
||||
var feeds = new List<string>();
|
||||
foreach (var feed in SelectedRule.AffectedFeeds)
|
||||
{
|
||||
foreach (var key in Feeds.Keys)
|
||||
{
|
||||
if (Feeds[key] == feed)
|
||||
{
|
||||
feeds.Add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
SelectedFeeds = feeds;
|
||||
}
|
||||
|
||||
protected void Cancel()
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
|
||||
protected async Task Submit()
|
||||
{
|
||||
if (SelectedRuleName is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await ApiClient.SetRssAutoDownloadingRule(SelectedRuleName, SelectedRule);
|
||||
|
||||
MatchingArticles = await ApiClient.GetRssMatchingArticles(SelectedRuleName);
|
||||
|
||||
_unsavedRuleNames.Remove(SelectedRuleName);
|
||||
}
|
||||
}
|
||||
}
|
||||
43
Lantean.QBTMud/Components/Dialogs/ShareRatioDialog.razor
Normal file
43
Lantean.QBTMud/Components/Dialogs/ShareRatioDialog.razor
Normal file
@@ -0,0 +1,43 @@
|
||||
@inherits SubmittableDialog
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudRadioGroup T="int" Value="ShareRatioType" ValueChanged="ShareRatioTypeChanged">
|
||||
<MudItem xs="12">
|
||||
<MudRadio T="int" Value="-2">Use global share limit</MudRadio>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudRadio T="int" Value="-1">Set no share limit</MudRadio>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudRadio T="int" Value="0">Set share limit to</MudRadio>
|
||||
</MudItem>
|
||||
</MudRadioGroup>
|
||||
</MudItem>
|
||||
<MudItem xs="3">
|
||||
<FieldSwitch Label="Ratio" Value="RatioEnabled" ValueChanged="RatioEnabledChanged" Disabled="@(!CustomEnabled)" />
|
||||
</MudItem>
|
||||
<MudItem xs="9">
|
||||
<MudNumericField T="float" Value="Ratio" ValueChanged="RatioChanged" Disabled="@(!(CustomEnabled && RatioEnabled))" Min="0" Max="1024000" Step="0.1F" Format="F2" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="3">
|
||||
<FieldSwitch Label="Total minutes" Value="TotalMinutesEnabled" ValueChanged="TotalMinutesEnabledChanged" Disabled="@(!CustomEnabled)" />
|
||||
</MudItem>
|
||||
<MudItem xs="9">
|
||||
<MudNumericField T="int" Value="TotalMinutes" ValueChanged="TotalMinutesChanged" Disabled="@(!(CustomEnabled && TotalMinutesEnabled))" Min="1" Max="1024000" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="minutes" />
|
||||
</MudItem>
|
||||
<MudItem xs="3">
|
||||
<FieldSwitch Label="Inactive minutes" Value="InactiveMinutesEnabled" ValueChanged="InactiveMinutesEnabledChanged" Disabled="@(!CustomEnabled)" />
|
||||
</MudItem>
|
||||
<MudItem xs="9">
|
||||
<MudNumericField T="int" Value="InactiveMinutes" ValueChanged="InactiveMinutesChanged" Disabled="@(!(CustomEnabled && InactiveMinutesEnabled))" Min="1" Max="1024000" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="minutes" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Cancel</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="Submit">Save</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
136
Lantean.QBTMud/Components/Dialogs/ShareRatioDialog.razor.cs
Normal file
136
Lantean.QBTMud/Components/Dialogs/ShareRatioDialog.razor.cs
Normal file
@@ -0,0 +1,136 @@
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBTMud.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMud.Components.Dialogs
|
||||
{
|
||||
public partial class ShareRatioDialog
|
||||
{
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public string? Label { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public ShareRatioMax? Value { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
protected int ShareRatioType { get; set; }
|
||||
|
||||
protected bool RatioEnabled { get; set; }
|
||||
|
||||
protected float Ratio { get; set; }
|
||||
|
||||
protected bool TotalMinutesEnabled { get; set; }
|
||||
|
||||
protected int TotalMinutes { get; set; }
|
||||
|
||||
protected bool InactiveMinutesEnabled { get; set; }
|
||||
|
||||
protected int InactiveMinutes { get; set; }
|
||||
|
||||
protected bool CustomEnabled => ShareRatioType == 0;
|
||||
|
||||
protected void RatioEnabledChanged(bool value)
|
||||
{
|
||||
RatioEnabled = value;
|
||||
}
|
||||
|
||||
protected void RatioChanged(float value)
|
||||
{
|
||||
Ratio = value;
|
||||
}
|
||||
|
||||
protected void TotalMinutesEnabledChanged(bool value)
|
||||
{
|
||||
TotalMinutesEnabled = value;
|
||||
}
|
||||
|
||||
protected void TotalMinutesChanged(int value)
|
||||
{
|
||||
TotalMinutes = value;
|
||||
}
|
||||
|
||||
protected void InactiveMinutesEnabledChanged(bool value)
|
||||
{
|
||||
InactiveMinutesEnabled = value;
|
||||
}
|
||||
|
||||
protected void InactiveMinutesChanged(int value)
|
||||
{
|
||||
InactiveMinutes = value;
|
||||
}
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
if (Value is null || Value.RatioLimit == Limits.GlobalLimit && Value.SeedingTimeLimit == Limits.GlobalLimit && Value.InactiveSeedingTimeLimit == Limits.GlobalLimit)
|
||||
{
|
||||
ShareRatioType = Limits.GlobalLimit;
|
||||
}
|
||||
else if (Value.MaxRatio == Limits.NoLimit && Value.MaxSeedingTime == Limits.NoLimit && Value.MaxInactiveSeedingTime == Limits.NoLimit)
|
||||
{
|
||||
ShareRatioType = Limits.NoLimit;
|
||||
}
|
||||
else
|
||||
{
|
||||
ShareRatioType = 0;
|
||||
if (Value.RatioLimit >= 0)
|
||||
{
|
||||
RatioEnabled = true;
|
||||
Ratio = Value.RatioLimit;
|
||||
}
|
||||
if (Value.SeedingTimeLimit >= 0)
|
||||
{
|
||||
TotalMinutesEnabled = true;
|
||||
TotalMinutes = (int)Value.SeedingTimeLimit;
|
||||
}
|
||||
if (Value.InactiveSeedingTimeLimit >= 0)
|
||||
{
|
||||
InactiveMinutesEnabled = true;
|
||||
InactiveMinutes = (int)Value.InactiveSeedingTimeLimit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void ShareRatioTypeChanged(int value)
|
||||
{
|
||||
ShareRatioType = value;
|
||||
}
|
||||
|
||||
protected void Cancel()
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
|
||||
protected void Submit()
|
||||
{
|
||||
var result = new ShareRatio();
|
||||
if (ShareRatioType == Limits.GlobalLimit)
|
||||
{
|
||||
result.RatioLimit = result.SeedingTimeLimit = result.InactiveSeedingTimeLimit = Limits.GlobalLimit;
|
||||
}
|
||||
else if (ShareRatioType == Limits.NoLimit)
|
||||
{
|
||||
result.RatioLimit = result.SeedingTimeLimit = result.InactiveSeedingTimeLimit = Limits.NoLimit;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.RatioLimit = RatioEnabled ? Ratio : Limits.NoLimit;
|
||||
result.SeedingTimeLimit = TotalMinutesEnabled ? TotalMinutes : Limits.NoLimit;
|
||||
result.InactiveSeedingTimeLimit = InactiveMinutesEnabled ? InactiveMinutes : Limits.NoLimit;
|
||||
}
|
||||
MudDialog.Close(DialogResult.Ok(result));
|
||||
}
|
||||
|
||||
protected override Task Submit(KeyboardEvent keyboardEvent)
|
||||
{
|
||||
Submit();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
30
Lantean.QBTMud/Components/Dialogs/SliderFieldDialog.razor
Normal file
30
Lantean.QBTMud/Components/Dialogs/SliderFieldDialog.razor
Normal file
@@ -0,0 +1,30 @@
|
||||
@typeparam T
|
||||
@inherits SubmittableDialog
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="@Label" Value="@GetDisplayValue()" ValueChanged="ValueChanged" Disabled="Disabled" Variant="Variant.Outlined" Adornment="@Adornment" AdornmentText="@AdornmentText" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSlider T="T" ValueLabel="true" Value="@Value" ValueChanged="ValueChanged" Min="@Min" Max="@Max" Disabled="Disabled">
|
||||
<ValueLabelContent>
|
||||
@if (ValueDisplayFunc is not null)
|
||||
{
|
||||
@ValueDisplayFunc(context.Value)
|
||||
}
|
||||
else
|
||||
{
|
||||
@context.Value
|
||||
}
|
||||
</ValueLabelContent>
|
||||
</MudSlider>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Cancel</MudButton>
|
||||
<MudButton Color="Color.Primary" ButtonType="ButtonType.Submit" OnClick="Submit">Save</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
88
Lantean.QBTMud/Components/Dialogs/SliderFieldDialog.razor.cs
Normal file
88
Lantean.QBTMud/Components/Dialogs/SliderFieldDialog.razor.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using Lantean.QBTMud.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Lantean.QBTMud.Components.Dialogs
|
||||
{
|
||||
public partial class SliderFieldDialog<T> where T : struct, INumber<T>
|
||||
{
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public string? Label { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public T Value { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public T Min { get; set; } = T.Zero;
|
||||
|
||||
[Parameter]
|
||||
public T Max { get; set; } = T.One;
|
||||
|
||||
[Parameter]
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<T, string>? ValueDisplayFunc { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<string, T>? ValueGetFunc { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Adornment Adornment { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string? AdornmentText { get; set; }
|
||||
|
||||
private string? GetDisplayValue()
|
||||
{
|
||||
var value = ValueDisplayFunc?.Invoke(Value);
|
||||
return value is null ? Value.ToString() : value;
|
||||
}
|
||||
|
||||
protected void ValueChanged(T value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
protected void ValueChanged(string value)
|
||||
{
|
||||
if (ValueGetFunc is not null)
|
||||
{
|
||||
T val = ValueGetFunc.Invoke(value);
|
||||
Value = val;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (T.TryParse(value, null, out var result))
|
||||
{
|
||||
Value = result;
|
||||
}
|
||||
else
|
||||
{
|
||||
Value = Min;
|
||||
}
|
||||
}
|
||||
|
||||
protected void Cancel()
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
|
||||
protected void Submit()
|
||||
{
|
||||
MudDialog.Close(DialogResult.Ok(Value));
|
||||
}
|
||||
|
||||
protected override Task Submit(KeyboardEvent keyboardEvent)
|
||||
{
|
||||
Submit();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
15
Lantean.QBTMud/Components/Dialogs/StringFieldDialog.razor
Normal file
15
Lantean.QBTMud/Components/Dialogs/StringFieldDialog.razor
Normal file
@@ -0,0 +1,15 @@
|
||||
@inherits SubmittableDialog
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="@Label" Value="@Value" ValueChanged="ValueChanged" Disabled="Disabled" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Cancel</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="Submit">Save</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
43
Lantean.QBTMud/Components/Dialogs/StringFieldDialog.razor.cs
Normal file
43
Lantean.QBTMud/Components/Dialogs/StringFieldDialog.razor.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using Lantean.QBTMud.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMud.Components.Dialogs
|
||||
{
|
||||
public partial class StringFieldDialog
|
||||
{
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public string? Label { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string? Value { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
protected void ValueChanged(string value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
protected void Cancel()
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
|
||||
protected void Submit()
|
||||
{
|
||||
MudDialog.Close(DialogResult.Ok(Value));
|
||||
}
|
||||
|
||||
protected override Task Submit(KeyboardEvent keyboardEvent)
|
||||
{
|
||||
Submit();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Lantean.QBTMud/Components/Dialogs/SubMenuDialog.razor
Normal file
11
Lantean.QBTMud/Components/Dialogs/SubMenuDialog.razor
Normal file
@@ -0,0 +1,11 @@
|
||||
<MudDialog ContentStyle="mix-width: 400px">
|
||||
<DialogContent>
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<CascadingValue Value="Preferences">
|
||||
<TorrentActions Hashes="Hashes" ParentAction="ParentAction" RenderType="RenderType.Children" MudDialog="MudDialog" Torrents="Torrents" Preferences="Preferences" />
|
||||
</CascadingValue>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</DialogContent>
|
||||
</MudDialog>
|
||||
36
Lantean.QBTMud/Components/Dialogs/SubMenuDialog.razor.cs
Normal file
36
Lantean.QBTMud/Components/Dialogs/SubMenuDialog.razor.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using Lantean.QBTMud.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMud.Components.Dialogs
|
||||
{
|
||||
public partial class SubMenuDialog
|
||||
{
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public UIAction? ParentAction { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Dictionary<string, Torrent> Torrents { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public QBitTorrentClient.Models.Preferences? Preferences { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public IEnumerable<string> Hashes { get; set; } = [];
|
||||
|
||||
protected Task CloseDialog()
|
||||
{
|
||||
MudDialog.Close();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected void Cancel()
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
46
Lantean.QBTMud/Components/Dialogs/SubmittableDialog.cs
Normal file
46
Lantean.QBTMud/Components/Dialogs/SubmittableDialog.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using Lantean.QBTMud.Models;
|
||||
using Lantean.QBTMud.Services;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Lantean.QBTMud.Components.Dialogs
|
||||
{
|
||||
public abstract class SubmittableDialog : ComponentBase, IAsyncDisposable
|
||||
{
|
||||
private bool _disposedValue;
|
||||
|
||||
[Inject]
|
||||
protected IKeyboardService KeyboardService { get; set; } = default!;
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
await KeyboardService.RegisterKeypressEvent("Enter", k => Submit(k));
|
||||
await KeyboardService.Focus();
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract Task Submit(KeyboardEvent keyboardEvent);
|
||||
|
||||
protected virtual async ValueTask DisposeAsync(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
await KeyboardService.UnregisterKeypressEvent("Enter");
|
||||
await KeyboardService.UnFocus();
|
||||
}
|
||||
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
await DisposeAsync(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
13
Lantean.QBTMud/Components/Dialogs/TorrentOptionsDialog.razor
Normal file
13
Lantean.QBTMud/Components/Dialogs/TorrentOptionsDialog.razor
Normal file
@@ -0,0 +1,13 @@
|
||||
@inherits SubmittableDialog
|
||||
|
||||
<MudDialog ContentStyle="mix-width: 400px">
|
||||
<DialogContent>
|
||||
<MudGrid>
|
||||
|
||||
</MudGrid>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Cancel</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="Submit">Save</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
@@ -0,0 +1,59 @@
|
||||
using Lantean.QBTMud.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMud.Components.Dialogs
|
||||
{
|
||||
public partial class TorrentOptionsDialog
|
||||
{
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
[EditorRequired]
|
||||
public string Hash { get; set; } = default!;
|
||||
|
||||
[CascadingParameter]
|
||||
public MainData MainData { get; set; } = default!;
|
||||
|
||||
[CascadingParameter]
|
||||
public QBitTorrentClient.Models.Preferences Preferences { get; set; } = default!;
|
||||
|
||||
protected bool AutomaticTorrentManagement { get; set; }
|
||||
|
||||
protected string? SavePath { get; set; }
|
||||
|
||||
protected string? TempPath { get; set; }
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
if (!MainData.Torrents.TryGetValue(Hash, out var torrent))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var tempPath = Preferences.TempPath;
|
||||
|
||||
AutomaticTorrentManagement = torrent.AutomaticTorrentManagement;
|
||||
SavePath = torrent.SavePath;
|
||||
TempPath = tempPath;
|
||||
}
|
||||
|
||||
protected void Cancel()
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
|
||||
protected void Submit()
|
||||
{
|
||||
MudDialog.Close();
|
||||
}
|
||||
|
||||
protected override Task Submit(KeyboardEvent keyboardEvent)
|
||||
{
|
||||
Submit();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
56
Lantean.QBTMud/Components/EnhancedErrorBoundary.cs
Normal file
56
Lantean.QBTMud/Components/EnhancedErrorBoundary.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Rendering;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Runtime.ExceptionServices;
|
||||
|
||||
namespace Lantean.QBTMud.Components
|
||||
{
|
||||
public class EnhancedErrorBoundary : ErrorBoundaryBase
|
||||
{
|
||||
private readonly ObservableCollection<Exception> _exceptions = [];
|
||||
|
||||
public bool HasErrored => CurrentException != null;
|
||||
|
||||
[Parameter]
|
||||
public EventCallback OnClear { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
[Inject]
|
||||
public ILogger<EnhancedErrorBoundary> Logger { get; set; } = default!;
|
||||
|
||||
protected override Task OnErrorAsync(Exception exception)
|
||||
{
|
||||
Logger.LogError(exception, "An application error occurred: {message}.", exception.Message);
|
||||
_exceptions.Add(exception);
|
||||
|
||||
if (Disabled)
|
||||
{
|
||||
ExceptionDispatchInfo.Capture(exception).Throw();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task RecoverAndClearErrors()
|
||||
{
|
||||
Recover();
|
||||
|
||||
return ClearErrors();
|
||||
}
|
||||
|
||||
public async Task ClearErrors()
|
||||
{
|
||||
_exceptions.Clear();
|
||||
await OnClear.InvokeAsync();
|
||||
}
|
||||
|
||||
public IReadOnlyList<Exception> Errors => _exceptions.AsReadOnly();
|
||||
|
||||
protected override void BuildRenderTree(RenderTreeBuilder builder)
|
||||
{
|
||||
builder.AddContent(0, ChildContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
9
Lantean.QBTMud/Components/ErrorDisplay.razor
Normal file
9
Lantean.QBTMud/Components/ErrorDisplay.razor
Normal file
@@ -0,0 +1,9 @@
|
||||
<MudList T="string">
|
||||
<MudListItem OnClick="ClearErrors">Clear Errors</MudListItem>
|
||||
<MudListItem OnClick="ClearErrorsAndResumeAsync">Clear Errors and Resume</MudListItem>
|
||||
<MudDivider />
|
||||
@foreach (var error in Errors)
|
||||
{
|
||||
<MudListItem OnClick="@(e => ShowException(error))">@error.Message</MudListItem>
|
||||
}
|
||||
</MudList>
|
||||
39
Lantean.QBTMud/Components/ErrorDisplay.razor.cs
Normal file
39
Lantean.QBTMud/Components/ErrorDisplay.razor.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using Lantean.QBTMud.Components.Dialogs;
|
||||
using Lantean.QBTMud.Helpers;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMud.Components
|
||||
{
|
||||
public partial class ErrorDisplay
|
||||
{
|
||||
[Inject]
|
||||
protected IDialogService DialogService { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
[EditorRequired]
|
||||
public EnhancedErrorBoundary ErrorBoundary { get; set; } = default!;
|
||||
|
||||
protected IEnumerable<Exception> Errors => ErrorBoundary.Errors;
|
||||
|
||||
protected async Task ShowException(Exception exception)
|
||||
{
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
{ nameof(ExceptionDialog.Exception), exception }
|
||||
};
|
||||
|
||||
await DialogService.ShowAsync<ExceptionDialog>("Error Details", parameters, DialogHelper.FormDialogOptions);
|
||||
}
|
||||
|
||||
protected async Task ClearErrors()
|
||||
{
|
||||
await ErrorBoundary.ClearErrors();
|
||||
}
|
||||
|
||||
protected async Task ClearErrorsAndResumeAsync()
|
||||
{
|
||||
await ErrorBoundary.RecoverAndClearErrors();
|
||||
}
|
||||
}
|
||||
}
|
||||
91
Lantean.QBTMud/Components/FilesTab.razor
Normal file
91
Lantean.QBTMud/Components/FilesTab.razor
Normal file
@@ -0,0 +1,91 @@
|
||||
<ContextMenu @ref="ContextMenu" Dense="true">
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.DriveFileRenameOutline" OnClick="RenameFileContextMenu">Rename</MudMenuItem>
|
||||
</ContextMenu>
|
||||
|
||||
<MudToolBar Gutters="false" Dense="true">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.DriveFileRenameOutline" OnClick="RenameFileToolbar" title="Rename" />
|
||||
<MudDivider Vertical="true" />
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.ViewColumn" Color="Color.Inherit" OnClick="ColumnOptions" title="Choose Columns" />
|
||||
<MudDivider Vertical="true" />
|
||||
<MudMenu Icon="@Icons.Material.Outlined.FileDownloadOff" Label="Do Not Download" AnchorOrigin="Origin.BottomLeft" TransformOrigin="Origin.TopLeft" title="Do Not Download">
|
||||
<MudMenuItem OnClick="DoNotDownloadLessThan100PercentAvailability">Less Than 100% Availability</MudMenuItem>
|
||||
<MudMenuItem OnClick="DoNotDownloadLessThan80PercentAvailability">Less than 80% Availability</MudMenuItem>
|
||||
<MudMenuItem OnClick="DoNotDownloadCurrentlyFilteredFiles">Currently Filtered Files</MudMenuItem>
|
||||
</MudMenu>
|
||||
<MudMenu Icon="@Icons.Material.Outlined.FileDownload" Label="Normal Priority" AnchorOrigin="Origin.BottomLeft" TransformOrigin="Origin.TopLeft" title="Download">
|
||||
<MudMenuItem OnClick="NormalPriorityLessThan100PercentAvailability">Less Than 100% Availability</MudMenuItem>
|
||||
<MudMenuItem OnClick="NormalPriorityLessThan80PercentAvailability">Less than 80% Availability</MudMenuItem>
|
||||
<MudMenuItem OnClick="NormalPriorityCurrentlyFilteredFiles">Currently Filtered Files</MudMenuItem>
|
||||
</MudMenu>
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.FilterList" OnClick="ShowFilterDialog" title="Filter" />
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.FilterListOff" OnClick="RemoveFilter" title="Remove Filter" />
|
||||
<MudSpacer />
|
||||
<MudTextField T="string" Value="SearchText" ValueChanged="SearchTextChanged" Immediate="true" DebounceInterval="500" Placeholder="Filter file list" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="mt-0"></MudTextField>
|
||||
</MudToolBar>
|
||||
|
||||
<DynamicTable
|
||||
@ref="Table"
|
||||
T="ContentItem"
|
||||
ColumnDefinitions="Columns"
|
||||
Items="Files"
|
||||
MultiSelection="false"
|
||||
SelectOnRowClick="true"
|
||||
PreSorted="true"
|
||||
SelectedItemChanged="SelectedItemChanged"
|
||||
SortColumnChanged="SortColumnChanged"
|
||||
SortDirectionChanged="SortDirectionChanged"
|
||||
OnTableDataContextMenu="TableDataContextMenu"
|
||||
OnTableDataLongPress="TableDataLongPress"
|
||||
Class="file-list"
|
||||
/>
|
||||
|
||||
@code {
|
||||
private RenderFragment<RowContext<ContentItem>> NameColumn
|
||||
{
|
||||
get
|
||||
{
|
||||
return context => __builder =>
|
||||
{
|
||||
<div style="@($"margin-left: {(context.Data.Level * 14) + (context.Data.Level >= 1 ? 16 : 0)}px")">
|
||||
@if (context.Data.IsFolder)
|
||||
{
|
||||
<MudIconButton Class="folder-button" Edge="Edge.Start" ButtonType="ButtonType.Button" Icon="@(ExpandedNodes.Contains(context.Data.Name) ? Icons.Material.Filled.KeyboardArrowDown : Icons.Material.Filled.KeyboardArrowRight)" OnClick="@(c => ToggleNode(context.Data))"></MudIconButton>
|
||||
<MudIcon Icon="@Icons.Material.Filled.Folder" Class="pt-0" Style="margin-right: 4px; position: relative; top: 7px; margin-left: -15px" />
|
||||
}
|
||||
@context.Data.DisplayName
|
||||
</div>;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private RenderFragment<RowContext<ContentItem>> PriorityColumn
|
||||
{
|
||||
get
|
||||
{
|
||||
return context => __builder =>
|
||||
{
|
||||
<MudSelect T="Priority" Dense="true" Value="@context.Data.Priority" ValueChanged="@(priority => PriorityValueChanged(context.Data, priority))" Class="mt-0">
|
||||
<MudSelectItem T="Priority" Value="Priority.DoNotDownload">Do not download</MudSelectItem>
|
||||
<MudSelectItem T="Priority" Value="Priority.Normal">Normal</MudSelectItem>
|
||||
<MudSelectItem T="Priority" Value="Priority.High">High</MudSelectItem>
|
||||
<MudSelectItem T="Priority" Value="Priority.Maximum">Maximum</MudSelectItem>
|
||||
</MudSelect>
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static RenderFragment<RowContext<ContentItem>> ProgressBarColumn
|
||||
{
|
||||
get
|
||||
{
|
||||
return context => __builder =>
|
||||
{
|
||||
var value = (float?)context.GetValue();
|
||||
var color = value < 1 ? Color.Success : Color.Info;
|
||||
<MudProgressLinear title="Progress" Color="@color" Value="@((value ?? 0) * 100)" Class="progress-expand" Size="Size.Large">
|
||||
@DisplayHelpers.Percentage(value)
|
||||
</MudProgressLinear>;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
555
Lantean.QBTMud/Components/FilesTab.razor.cs
Normal file
555
Lantean.QBTMud/Components/FilesTab.razor.cs
Normal file
@@ -0,0 +1,555 @@
|
||||
using Blazored.LocalStorage;
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBTMud.Components.Dialogs;
|
||||
using Lantean.QBTMud.Components.UI;
|
||||
using Lantean.QBTMud.Filter;
|
||||
using Lantean.QBTMud.Helpers;
|
||||
using Lantean.QBTMud.Models;
|
||||
using Lantean.QBTMud.Services;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Net;
|
||||
|
||||
namespace Lantean.QBTMud.Components
|
||||
{
|
||||
public partial class FilesTab : IAsyncDisposable
|
||||
{
|
||||
private readonly bool _refreshEnabled = true;
|
||||
private const string _expandedNodesStorageKey = "FilesTab.ExpandedNodes";
|
||||
|
||||
private readonly CancellationTokenSource _timerCancellationToken = new();
|
||||
private bool _disposedValue;
|
||||
|
||||
private List<PropertyFilterDefinition<ContentItem>>? _filterDefinitions;
|
||||
private readonly Dictionary<string, RenderFragment<RowContext<ContentItem>>> _columnRenderFragments = [];
|
||||
|
||||
private string? _previousHash;
|
||||
private string? _sortColumn;
|
||||
private SortDirection _sortDirection;
|
||||
|
||||
[Parameter]
|
||||
public bool Active { get; set; }
|
||||
|
||||
[Parameter, EditorRequired]
|
||||
public string? Hash { get; set; }
|
||||
|
||||
[CascadingParameter(Name = "RefreshInterval")]
|
||||
public int RefreshInterval { get; set; }
|
||||
|
||||
[Inject]
|
||||
protected IApiClient ApiClient { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected IDialogService DialogService { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected ILocalStorageService LocalStorage { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected IDataManager DataManager { get; set; } = default!;
|
||||
|
||||
protected HashSet<string> ExpandedNodes { get; set; } = [];
|
||||
|
||||
protected Dictionary<string, ContentItem>? FileList { get; set; }
|
||||
|
||||
protected IEnumerable<ContentItem> Files => GetFiles();
|
||||
|
||||
protected ContentItem? SelectedItem { get; set; }
|
||||
|
||||
protected ContentItem? ContextMenuItem { get; set; }
|
||||
|
||||
protected string? SearchText { get; set; }
|
||||
|
||||
public IEnumerable<Func<ContentItem, bool>>? Filters { get; set; }
|
||||
|
||||
private DynamicTable<ContentItem>? Table { get; set; }
|
||||
|
||||
private ContextMenu? ContextMenu { get; set; }
|
||||
|
||||
public FilesTab()
|
||||
{
|
||||
_columnRenderFragments.Add("Name", NameColumn);
|
||||
_columnRenderFragments.Add("Priority", PriorityColumn);
|
||||
}
|
||||
|
||||
protected async Task ColumnOptions()
|
||||
{
|
||||
if (Table is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await Table.ShowColumnOptionsDialog();
|
||||
}
|
||||
|
||||
protected async Task ShowFilterDialog()
|
||||
{
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
{ nameof(FilterOptionsDialog<ContentItem>.FilterDefinitions), _filterDefinitions },
|
||||
};
|
||||
|
||||
var result = await DialogService.ShowAsync<FilterOptionsDialog<ContentItem>>("Filters", parameters, DialogHelper.FormDialogOptions);
|
||||
|
||||
var dialogResult = await result.Result;
|
||||
if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_filterDefinitions = (List<PropertyFilterDefinition<ContentItem>>?)dialogResult.Data;
|
||||
if (_filterDefinitions is null)
|
||||
{
|
||||
Filters = null;
|
||||
return;
|
||||
}
|
||||
|
||||
var filters = new List<Func<ContentItem, bool>>();
|
||||
foreach (var filterDefinition in _filterDefinitions)
|
||||
{
|
||||
var expression = Filter.FilterExpressionGenerator.GenerateExpression(filterDefinition, false);
|
||||
filters.Add(expression.Compile());
|
||||
}
|
||||
|
||||
Filters = filters;
|
||||
}
|
||||
|
||||
protected void RemoveFilter()
|
||||
{
|
||||
Filters = null;
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
await DisposeAsync(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual async Task DisposeAsync(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing && Files is not null)
|
||||
{
|
||||
_timerCancellationToken.Cancel();
|
||||
_timerCancellationToken.Dispose();
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected static Priority GetPriority(IEnumerable<ContentItem> items)
|
||||
{
|
||||
var distinctPriorities = items.Select(i => i.Priority).Distinct();
|
||||
if (distinctPriorities.Count() == 1)
|
||||
{
|
||||
return distinctPriorities.First();
|
||||
}
|
||||
|
||||
return Priority.Mixed;
|
||||
}
|
||||
|
||||
protected void SearchTextChanged(string value)
|
||||
{
|
||||
SearchText = value;
|
||||
}
|
||||
|
||||
protected Task TableDataContextMenu(TableDataContextMenuEventArgs<ContentItem> eventArgs)
|
||||
{
|
||||
return ShowContextMenu(eventArgs.Item, eventArgs.MouseEventArgs);
|
||||
}
|
||||
|
||||
protected Task TableDataLongPress(TableDataLongPressEventArgs<ContentItem> eventArgs)
|
||||
{
|
||||
return ShowContextMenu(eventArgs.Item, eventArgs.LongPressEventArgs);
|
||||
}
|
||||
|
||||
private async Task ShowContextMenu(ContentItem? contentItem, EventArgs eventArgs)
|
||||
{
|
||||
ContextMenuItem = contentItem;
|
||||
|
||||
if (ContextMenu is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await ContextMenu.OpenMenuAsync(eventArgs);
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (!_refreshEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!firstRender)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(RefreshInterval)))
|
||||
{
|
||||
while (!_timerCancellationToken.IsCancellationRequested && await timer.WaitForNextTickAsync())
|
||||
{
|
||||
if (Active && Hash is not null)
|
||||
{
|
||||
IReadOnlyList<QBitTorrentClient.Models.FileData> files;
|
||||
try
|
||||
{
|
||||
files = await ApiClient.GetTorrentContents(Hash);
|
||||
}
|
||||
catch (HttpRequestException exception) when (exception.StatusCode == HttpStatusCode.Forbidden || exception.StatusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
_timerCancellationToken.CancelIfNotDisposed();
|
||||
return;
|
||||
}
|
||||
|
||||
if (FileList is null)
|
||||
{
|
||||
FileList = DataManager.CreateContentsList(files);
|
||||
}
|
||||
else
|
||||
{
|
||||
DataManager.MergeContentsList(files, FileList);
|
||||
}
|
||||
}
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (Hash is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Active)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Hash == _previousHash)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_previousHash = Hash;
|
||||
|
||||
var contents = await ApiClient.GetTorrentContents(Hash);
|
||||
FileList = DataManager.CreateContentsList(contents);
|
||||
|
||||
var expandedNodes = await LocalStorage.GetItemAsync<HashSet<string>>($"{_expandedNodesStorageKey}.{Hash}");
|
||||
if (expandedNodes is not null)
|
||||
{
|
||||
ExpandedNodes = expandedNodes;
|
||||
}
|
||||
else
|
||||
{
|
||||
ExpandedNodes.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task PriorityValueChanged(ContentItem contentItem, Priority priority)
|
||||
{
|
||||
if (Hash is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IEnumerable<int> fileIndexes;
|
||||
if (contentItem.IsFolder)
|
||||
{
|
||||
fileIndexes = GetDescendants(contentItem).Where(c => !c.IsFolder).Select(c => c.Index);
|
||||
}
|
||||
else
|
||||
{
|
||||
fileIndexes = [contentItem.Index];
|
||||
}
|
||||
|
||||
await ApiClient.SetFilePriority(Hash, fileIndexes, MapPriority(priority));
|
||||
}
|
||||
|
||||
protected Task RenameFileToolbar()
|
||||
{
|
||||
if (SelectedItem is null)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
return RenameFiles(SelectedItem);
|
||||
}
|
||||
|
||||
protected Task RenameFileContextMenu()
|
||||
{
|
||||
if (ContextMenuItem is null)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
return RenameFiles(ContextMenuItem);
|
||||
}
|
||||
|
||||
private async Task RenameFiles(params ContentItem[] contentItems)
|
||||
{
|
||||
if (Hash is null || contentItems.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (contentItems.Length == 1)
|
||||
{
|
||||
var contentItem = contentItems[0];
|
||||
var name = contentItem.GetFileName();
|
||||
await DialogService.InvokeStringFieldDialog("Rename", "New name", name, async value => await ApiClient.RenameFile(Hash, contentItem.Name, contentItem.Path + value));
|
||||
}
|
||||
else
|
||||
{
|
||||
await DialogService.InvokeRenameFilesDialog(Hash);
|
||||
}
|
||||
}
|
||||
|
||||
protected void SortColumnChanged(string sortColumn)
|
||||
{
|
||||
_sortColumn = sortColumn;
|
||||
}
|
||||
|
||||
protected void SortDirectionChanged(SortDirection sortDirection)
|
||||
{
|
||||
_sortDirection = sortDirection;
|
||||
}
|
||||
|
||||
protected void SelectedItemChanged(ContentItem item)
|
||||
{
|
||||
SelectedItem = item;
|
||||
}
|
||||
|
||||
protected async Task ToggleNode(ContentItem contentItem)
|
||||
{
|
||||
if (ExpandedNodes.Contains(contentItem.Name))
|
||||
{
|
||||
ExpandedNodes.Remove(contentItem.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
ExpandedNodes.Add(contentItem.Name);
|
||||
}
|
||||
|
||||
await LocalStorage.SetItemAsync($"{_expandedNodesStorageKey}.{Hash}", ExpandedNodes);
|
||||
}
|
||||
|
||||
private static QBitTorrentClient.Models.Priority MapPriority(Priority priority)
|
||||
{
|
||||
return (QBitTorrentClient.Models.Priority)(int)priority;
|
||||
}
|
||||
|
||||
private Func<ContentItem, object?> GetSortSelector()
|
||||
{
|
||||
var sortSelector = ColumnsDefinitions.Find(c => c.Id == _sortColumn)?.SortSelector;
|
||||
|
||||
return sortSelector ?? (i => i.Name);
|
||||
}
|
||||
|
||||
private IEnumerable<ContentItem> GetDescendants(ContentItem contentItem)
|
||||
{
|
||||
if (!contentItem.IsFolder || Files is null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
return FileList!.Values.Where(f => f.Name.StartsWith(contentItem.Name + Extensions.DirectorySeparator) && !f.IsFolder);
|
||||
}
|
||||
|
||||
private IEnumerable<ContentItem> GetChildren(ContentItem folder, int level)
|
||||
{
|
||||
level++;
|
||||
var descendantsKey = folder.GetDescendantsKey(level);
|
||||
|
||||
foreach (var item in FileList!.Values.Where(f => f.Name.StartsWith(descendantsKey) && f.Level == level).OrderByDirection(_sortDirection, GetSortSelector()))
|
||||
{
|
||||
if (item.IsFolder)
|
||||
{
|
||||
var descendants = GetChildren(item, level);
|
||||
// if the filter returns some results then show folder item
|
||||
if (descendants.Any())
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
|
||||
// if the folder is not expanded - don't return children
|
||||
if (!ExpandedNodes.Contains(item.Name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// then show children
|
||||
foreach (var descendant in descendants)
|
||||
{
|
||||
yield return descendant;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (FilterContentItem(item))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool FilterContentItem(ContentItem item)
|
||||
{
|
||||
if (Filters is not null)
|
||||
{
|
||||
foreach (var filter in Filters)
|
||||
{
|
||||
var result = filter(item);
|
||||
if (!result)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!FilterHelper.FilterTerms(item.Name, SearchText))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private ReadOnlyCollection<ContentItem> GetFiles()
|
||||
{
|
||||
if (FileList is null || FileList.Values.Count == 0)
|
||||
{
|
||||
return new ReadOnlyCollection<ContentItem>([]);
|
||||
}
|
||||
|
||||
var maxLevel = FileList.Values.Max(f => f.Level);
|
||||
// this is a flat file structure
|
||||
if (maxLevel == 0)
|
||||
{
|
||||
return FileList.Values.Where(FilterContentItem).OrderByDirection(_sortDirection, GetSortSelector()).ToList().AsReadOnly();
|
||||
}
|
||||
|
||||
var list = new List<ContentItem>();
|
||||
|
||||
var rootItems = FileList.Values.Where(c => c.Level == 0).OrderByDirection(_sortDirection, GetSortSelector()).ToList();
|
||||
foreach (var item in rootItems)
|
||||
{
|
||||
list.Add(item);
|
||||
|
||||
if (item.IsFolder && ExpandedNodes.Contains(item.Name))
|
||||
{
|
||||
var level = 0;
|
||||
var descendants = GetChildren(item, level);
|
||||
foreach (var descendant in descendants)
|
||||
{
|
||||
list.Add(descendant);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list.AsReadOnly();
|
||||
}
|
||||
|
||||
protected async Task DoNotDownloadLessThan100PercentAvailability()
|
||||
{
|
||||
await LessThanXAvailability(1f, QBitTorrentClient.Models.Priority.DoNotDownload);
|
||||
}
|
||||
|
||||
protected async Task DoNotDownloadLessThan80PercentAvailability()
|
||||
{
|
||||
await LessThanXAvailability(0.8f, QBitTorrentClient.Models.Priority.DoNotDownload);
|
||||
}
|
||||
|
||||
protected async Task DoNotDownloadCurrentlyFilteredFiles()
|
||||
{
|
||||
await CurrentlyFilteredFiles(QBitTorrentClient.Models.Priority.DoNotDownload);
|
||||
}
|
||||
|
||||
protected async Task NormalPriorityLessThan100PercentAvailability()
|
||||
{
|
||||
await LessThanXAvailability(1f, QBitTorrentClient.Models.Priority.Normal);
|
||||
}
|
||||
|
||||
protected async Task NormalPriorityLessThan80PercentAvailability()
|
||||
{
|
||||
await LessThanXAvailability(0.8f, QBitTorrentClient.Models.Priority.Normal);
|
||||
}
|
||||
|
||||
protected async Task NormalPriorityCurrentlyFilteredFiles()
|
||||
{
|
||||
await CurrentlyFilteredFiles(QBitTorrentClient.Models.Priority.Normal);
|
||||
}
|
||||
|
||||
private async Task LessThanXAvailability(float value, QBitTorrentClient.Models.Priority priority)
|
||||
{
|
||||
if (Hash is null || FileList is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var files = FileList.Values.Where(f => !f.IsFolder && f.Availability < value).Select(f => f.Index);
|
||||
|
||||
if (!files.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await ApiClient.SetFilePriority(Hash, files, priority);
|
||||
}
|
||||
|
||||
protected async Task CurrentlyFilteredFiles(QBitTorrentClient.Models.Priority priority)
|
||||
{
|
||||
if (Hash is null || FileList is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var files = GetFiles().Where(f => !f.IsFolder).Select(f => f.Index);
|
||||
|
||||
if (!files.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await ApiClient.SetFilePriority(Hash, files, priority);
|
||||
}
|
||||
|
||||
protected IEnumerable<ColumnDefinition<ContentItem>> Columns => GetColumnDefinitions();
|
||||
|
||||
private IEnumerable<ColumnDefinition<ContentItem>> GetColumnDefinitions()
|
||||
{
|
||||
foreach (var columnDefinition in ColumnsDefinitions)
|
||||
{
|
||||
if (_columnRenderFragments.TryGetValue(columnDefinition.Header, out var fragment))
|
||||
{
|
||||
columnDefinition.RowTemplate = fragment;
|
||||
}
|
||||
|
||||
yield return columnDefinition;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<ColumnDefinition<ContentItem>> ColumnsDefinitions { get; } =
|
||||
[
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<ContentItem>("Name", c => c.Name, width: 400, initialDirection: SortDirection.Ascending, classFunc: c => c.IsFolder ? "pa-0" : "pa-2"),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<ContentItem>("Total Size", c => c.Size, c => DisplayHelpers.Size(c.Size)),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition("Progress", c => c.Progress, ProgressBarColumn, tdClass: "table-progress pl-2 pr-2"),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<ContentItem>("Priority", c => c.Priority, tdClass: "table-select pa-0"),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<ContentItem>("Remaining", c => c.Remaining, c => DisplayHelpers.Size(c.Remaining)),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<ContentItem>("Availability", c => c.Availability, c => c.Availability.ToString("0.00")),
|
||||
];
|
||||
}
|
||||
}
|
||||
73
Lantean.QBTMud/Components/FiltersNav.razor
Normal file
73
Lantean.QBTMud/Components/FiltersNav.razor
Normal file
@@ -0,0 +1,73 @@
|
||||
<ContextMenu @ref="StatusContextMenu" Dense="true" AdjustmentY="-60">
|
||||
@TorrentControls(_statusType)
|
||||
</ContextMenu>
|
||||
|
||||
<ContextMenu @ref="CategoryContextMenu" Dense="true" AdjustmentY="-60">
|
||||
<MudMenuItem Icon="@Icons.Material.Outlined.AddCircle" IconColor="Color.Info" OnClick="AddCategory">Add category</MudMenuItem>
|
||||
@if (IsCategoryTarget)
|
||||
{
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Edit" IconColor="Color.Info" OnClick="EditCategory">Edit category</MudMenuItem>
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Delete" IconColor="Color.Error" OnClick="RemoveCategory">Remove category</MudMenuItem>
|
||||
}
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Delete" IconColor="Color.Error" OnClick="RemoveUnusedCategories">Remove unused categories</MudMenuItem>
|
||||
<MudDivider />
|
||||
@TorrentControls(_categoryType)
|
||||
</ContextMenu>
|
||||
|
||||
<ContextMenu @ref="TagContextMenu" Dense="true" AdjustmentY="-60">
|
||||
<MudMenuItem Icon="@Icons.Material.Outlined.AddCircle" IconColor="Color.Info" OnClick="AddTag">Add tag</MudMenuItem>
|
||||
@if (IsTagTarget)
|
||||
{
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Delete" IconColor="Color.Error" OnClick="RemoveTag">Remove tag</MudMenuItem>
|
||||
}
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Delete" IconColor="Color.Error" OnClick="RemoveUnusedTags">Remove unused tags</MudMenuItem>
|
||||
<MudDivider />
|
||||
@TorrentControls(_tagType)
|
||||
</ContextMenu>
|
||||
|
||||
<ContextMenu @ref="TrackerContextMenu" Dense="true" AdjustmentY="-60">
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Delete" IconColor="Color.Error" OnClick="RemoveUnusedCategories">Remove tracker</MudMenuItem>
|
||||
<MudDivider />
|
||||
@TorrentControls(_trackerType)
|
||||
</ContextMenu>
|
||||
|
||||
<MudNavMenu Dense="true">
|
||||
<MudNavGroup Title="Status" @bind-Expanded="_statusExpanded">
|
||||
@foreach (var (status, count) in Statuses)
|
||||
{
|
||||
var (icon, color) = DisplayHelpers.GetStatusIcon(status);
|
||||
<CustomNavLink Class="filter-menu-item" Active="@(Status == status)" Icon="@icon" IconColor="@color" OnClick="@(e => StatusValueChanged(status))" OnContextMenu="@(e => StatusOnContextMenu(e, status))" OnLongPress="@(e => StatusOnLongPress(e, status))">@($"{status.GetStatusName()} ({count})")</CustomNavLink>
|
||||
}
|
||||
</MudNavGroup>
|
||||
<MudNavGroup Title="Categories" @bind-Expanded="_categoriesExpanded">
|
||||
@foreach (var (category, count) in Categories)
|
||||
{
|
||||
<CustomNavLink Class="filter-menu-item" Active="@(Category == category)" Icon="@Icons.Material.Filled.List" IconColor="Color.Info" OnClick="@(e => CategoryValueChanged(category))" OnContextMenu="@(e => CategoryOnContextMenu(e, category))" OnLongPress="@(e => CategoryOnLongPress(e, category))">@($"{category} ({count})")</CustomNavLink>
|
||||
}
|
||||
</MudNavGroup>
|
||||
<MudNavGroup Title="Tags" @bind-Expanded="_tagsExpanded">
|
||||
@foreach (var (tag, count) in Tags)
|
||||
{
|
||||
<CustomNavLink Class="filter-menu-item" Active="@(Tag == tag)" Icon="@Icons.Material.Filled.Label" IconColor="Color.Info" OnClick="@(e => TagValueChanged(tag))" OnContextMenu="@(e => TagOnContextMenu(e, tag))" OnLongPress="@(e => TagOnLongPress(e, tag))">@($"{tag} ({count})")</CustomNavLink>
|
||||
}
|
||||
</MudNavGroup>
|
||||
<MudNavGroup Title="Trackers" @bind-Expanded="_trackersExpanded">
|
||||
@foreach (var (tracker, count) in Trackers)
|
||||
{
|
||||
<CustomNavLink Class="filter-menu-item" Active="@(Tracker == tracker)" Icon="@Icons.Material.Filled.PinDrop" IconColor="Color.Info" OnClick="@(e => TrackerValueChanged(tracker))" OnContextMenu="@(e => TrackerOnContextMenu(e, tracker))" OnLongPress="@(e => TrackerOnLongPress(e, tracker))">@($"{tracker} ({count})")</CustomNavLink>
|
||||
}
|
||||
</MudNavGroup>
|
||||
</MudNavMenu>
|
||||
|
||||
|
||||
@code {
|
||||
private RenderFragment TorrentControls(string type)
|
||||
{
|
||||
return __builder =>
|
||||
{
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.PlayArrow" IconColor="Color.Success" OnClick="@(e => ResumeTorrents(type))">Resume torrents</MudMenuItem>
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Pause" IconColor="Color.Warning" OnClick="@(e => PauseTorrents(type))">Pause torrents</MudMenuItem>
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Delete" IconColor="Color.Error" OnClick="@(e => RemoveTorrents(type))">Remove torrents</MudMenuItem>
|
||||
};
|
||||
}
|
||||
}
|
||||
474
Lantean.QBTMud/Components/FiltersNav.razor.cs
Normal file
474
Lantean.QBTMud/Components/FiltersNav.razor.cs
Normal file
@@ -0,0 +1,474 @@
|
||||
using Blazored.LocalStorage;
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBTMud.Components.UI;
|
||||
using Lantean.QBTMud.EventHandlers;
|
||||
using Lantean.QBTMud.Helpers;
|
||||
using Lantean.QBTMud.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMud.Components
|
||||
{
|
||||
public partial class FiltersNav
|
||||
{
|
||||
private const string _statusSelectionStorageKey = "FiltersNav.Selection.Status";
|
||||
private const string _categorySelectionStorageKey = "FiltersNav.Selection.Category";
|
||||
private const string _tagSelectionStorageKey = "FiltersNav.Selection.Tag";
|
||||
private const string _trackerSelectionStorageKey = "FiltersNav.Selection.Tracker";
|
||||
|
||||
private const string _statusType = nameof(_statusType);
|
||||
private const string _categoryType = nameof(_categoryType);
|
||||
private const string _tagType = nameof(_tagType);
|
||||
private const string _trackerType = nameof(_trackerType);
|
||||
|
||||
private bool _statusExpanded = true;
|
||||
private bool _categoriesExpanded = true;
|
||||
private bool _tagsExpanded = true;
|
||||
private bool _trackersExpanded = true;
|
||||
|
||||
protected string Status { get; set; } = Models.Status.All.ToString();
|
||||
|
||||
protected string Category { get; set; } = FilterHelper.CATEGORY_ALL;
|
||||
|
||||
protected string Tag { get; set; } = FilterHelper.TAG_ALL;
|
||||
|
||||
protected string Tracker { get; set; } = FilterHelper.TRACKER_ALL;
|
||||
|
||||
[Inject]
|
||||
protected ILocalStorageService LocalStorage { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected IDialogService DialogService { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected IApiClient ApiClient { get; set; } = default!;
|
||||
|
||||
[CascadingParameter]
|
||||
public MainData? MainData { get; set; }
|
||||
|
||||
[CascadingParameter]
|
||||
public QBitTorrentClient.Models.Preferences? Preferences { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<string> CategoryChanged { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<Status> StatusChanged { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<string> TagChanged { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<string> TrackerChanged { get; set; }
|
||||
|
||||
protected Dictionary<string, int> Tags => GetTags();
|
||||
|
||||
protected Dictionary<string, int> Categories => GetCategories();
|
||||
|
||||
protected Dictionary<string, int> Trackers => GetTrackers();
|
||||
|
||||
protected Dictionary<string, int> Statuses => GetStatuses();
|
||||
|
||||
protected ContextMenu? StatusContextMenu { get; set; }
|
||||
|
||||
protected ContextMenu? CategoryContextMenu { get; set; }
|
||||
|
||||
protected ContextMenu? TagContextMenu { get; set; }
|
||||
|
||||
protected ContextMenu? TrackerContextMenu { get; set; }
|
||||
|
||||
protected string? ContextMenuStatus { get; set; }
|
||||
|
||||
protected bool IsCategoryTarget { get; set; }
|
||||
|
||||
protected string? ContextMenuCategory { get; set; }
|
||||
|
||||
protected bool IsTagTarget { get; set; }
|
||||
|
||||
protected string? ContextMenuTag { get; set; }
|
||||
|
||||
protected string? ContextMenuTracker { get; set; }
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var status = await LocalStorage.GetItemAsStringAsync(_statusSelectionStorageKey);
|
||||
if (status is not null)
|
||||
{
|
||||
Status = status;
|
||||
await StatusChanged.InvokeAsync(Enum.Parse<Status>(status));
|
||||
}
|
||||
|
||||
var category = await LocalStorage.GetItemAsStringAsync(_categorySelectionStorageKey);
|
||||
if (category is not null)
|
||||
{
|
||||
Category = category;
|
||||
await CategoryChanged.InvokeAsync(category);
|
||||
}
|
||||
|
||||
var tag = await LocalStorage.GetItemAsStringAsync(_tagSelectionStorageKey);
|
||||
if (tag is not null)
|
||||
{
|
||||
Tag = tag;
|
||||
await TagChanged.InvokeAsync(tag);
|
||||
}
|
||||
|
||||
var tracker = await LocalStorage.GetItemAsStringAsync(_trackerSelectionStorageKey);
|
||||
if (tracker is not null)
|
||||
{
|
||||
Tracker = tracker;
|
||||
await TrackerChanged.InvokeAsync(tracker);
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task StatusValueChanged(string value)
|
||||
{
|
||||
Status = value;
|
||||
await StatusChanged.InvokeAsync(Enum.Parse<Status>(value));
|
||||
|
||||
if (value != Models.Status.All.ToString())
|
||||
{
|
||||
await LocalStorage.SetItemAsStringAsync(_statusSelectionStorageKey, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
await LocalStorage.RemoveItemAsync(_statusSelectionStorageKey);
|
||||
}
|
||||
}
|
||||
|
||||
protected Task StatusOnContextMenu(MouseEventArgs args, string value)
|
||||
{
|
||||
return ShowStatusContextMenu(args, value);
|
||||
}
|
||||
|
||||
protected Task StatusOnLongPress(LongPressEventArgs args, string value)
|
||||
{
|
||||
return ShowStatusContextMenu(args, value);
|
||||
}
|
||||
|
||||
protected Task ShowStatusContextMenu(EventArgs args, string value)
|
||||
{
|
||||
if (StatusContextMenu is null)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
ContextMenuStatus = value;
|
||||
|
||||
return StatusContextMenu.OpenMenuAsync(args);
|
||||
}
|
||||
|
||||
protected async Task CategoryValueChanged(string value)
|
||||
{
|
||||
Category = value;
|
||||
await CategoryChanged.InvokeAsync(value);
|
||||
|
||||
if (value != FilterHelper.CATEGORY_ALL)
|
||||
{
|
||||
await LocalStorage.SetItemAsStringAsync(_categorySelectionStorageKey, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
await LocalStorage.RemoveItemAsync(_categorySelectionStorageKey);
|
||||
}
|
||||
}
|
||||
|
||||
protected Task CategoryOnContextMenu(MouseEventArgs args, string value)
|
||||
{
|
||||
return ShowCategoryContextMenu(args, value);
|
||||
}
|
||||
|
||||
protected Task CategoryOnLongPress(LongPressEventArgs args, string value)
|
||||
{
|
||||
return ShowCategoryContextMenu(args, value);
|
||||
}
|
||||
|
||||
protected Task ShowCategoryContextMenu(EventArgs args, string value)
|
||||
{
|
||||
if (CategoryContextMenu is null)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
IsCategoryTarget = value != FilterHelper.CATEGORY_ALL && value != FilterHelper.CATEGORY_UNCATEGORIZED;
|
||||
ContextMenuCategory = value;
|
||||
|
||||
return CategoryContextMenu.OpenMenuAsync(args);
|
||||
}
|
||||
|
||||
protected async Task TagValueChanged(string value)
|
||||
{
|
||||
Tag = value;
|
||||
await TagChanged.InvokeAsync(value);
|
||||
|
||||
if (value != FilterHelper.TAG_ALL)
|
||||
{
|
||||
await LocalStorage.SetItemAsStringAsync(_tagSelectionStorageKey, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
await LocalStorage.RemoveItemAsync(_tagSelectionStorageKey);
|
||||
}
|
||||
}
|
||||
|
||||
protected Task TagOnContextMenu(MouseEventArgs args, string value)
|
||||
{
|
||||
return ShowTagContextMenu(args, value);
|
||||
}
|
||||
|
||||
protected Task TagOnLongPress(LongPressEventArgs args, string value)
|
||||
{
|
||||
return ShowTagContextMenu(args, value);
|
||||
}
|
||||
|
||||
protected Task ShowTagContextMenu(EventArgs args, string value)
|
||||
{
|
||||
if (TagContextMenu is null)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
IsTagTarget = value != FilterHelper.TAG_ALL && value != FilterHelper.TAG_UNTAGGED;
|
||||
ContextMenuTag = value;
|
||||
|
||||
return TagContextMenu.OpenMenuAsync(args);
|
||||
}
|
||||
|
||||
protected async Task TrackerValueChanged(string value)
|
||||
{
|
||||
Tracker = value;
|
||||
await TrackerChanged.InvokeAsync(value);
|
||||
|
||||
if (value != FilterHelper.TRACKER_ALL)
|
||||
{
|
||||
await LocalStorage.SetItemAsStringAsync(_trackerSelectionStorageKey, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
await LocalStorage.RemoveItemAsync(_trackerSelectionStorageKey);
|
||||
}
|
||||
}
|
||||
|
||||
protected Task TrackerOnContextMenu(MouseEventArgs args, string value)
|
||||
{
|
||||
return ShowTrackerContextMenu(args, value);
|
||||
}
|
||||
|
||||
protected Task TrackerOnLongPress(LongPressEventArgs args, string value)
|
||||
{
|
||||
return ShowTrackerContextMenu(args, value);
|
||||
}
|
||||
|
||||
protected Task ShowTrackerContextMenu(EventArgs args, string value)
|
||||
{
|
||||
if (TrackerContextMenu is null)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
ContextMenuTracker = value;
|
||||
|
||||
return TrackerContextMenu.OpenMenuAsync(args);
|
||||
}
|
||||
|
||||
protected async Task AddCategory()
|
||||
{
|
||||
await DialogService.InvokeAddCategoryDialog(ApiClient);
|
||||
}
|
||||
|
||||
protected async Task EditCategory()
|
||||
{
|
||||
if (ContextMenuCategory is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await DialogService.InvokeEditCategoryDialog(ApiClient, ContextMenuCategory);
|
||||
}
|
||||
|
||||
protected async Task RemoveCategory()
|
||||
{
|
||||
if (ContextMenuCategory is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await ApiClient.RemoveCategories(ContextMenuCategory);
|
||||
|
||||
Categories.Remove(ContextMenuCategory);
|
||||
}
|
||||
|
||||
protected async Task RemoveUnusedCategories()
|
||||
{
|
||||
var removedCategories = await ApiClient.RemoveUnusedCategories();
|
||||
|
||||
foreach (var removedCategory in removedCategories)
|
||||
{
|
||||
Categories.Remove(removedCategory);
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task AddTag()
|
||||
{
|
||||
if (ContextMenuTag is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var tags = await DialogService.ShowAddTagsDialog();
|
||||
if (tags is null || tags.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await ApiClient.CreateTags(tags);
|
||||
}
|
||||
|
||||
protected async Task RemoveTag()
|
||||
{
|
||||
if (ContextMenuTag is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await ApiClient.DeleteTags(ContextMenuTag);
|
||||
|
||||
Tags.Remove(ContextMenuTag);
|
||||
}
|
||||
|
||||
protected async Task RemoveUnusedTags()
|
||||
{
|
||||
var removedTags = await ApiClient.RemoveUnusedTags();
|
||||
|
||||
foreach (var removedTag in removedTags)
|
||||
{
|
||||
Tags.Remove(removedTag);
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task ResumeTorrents(string type)
|
||||
{
|
||||
var torrents = GetAffectedTorrentHashes(type);
|
||||
|
||||
await ApiClient.ResumeTorrents(torrents);
|
||||
}
|
||||
|
||||
protected async Task PauseTorrents(string type)
|
||||
{
|
||||
var torrents = GetAffectedTorrentHashes(type);
|
||||
|
||||
await ApiClient.PauseTorrents(torrents);
|
||||
}
|
||||
|
||||
protected async Task RemoveTorrents(string type)
|
||||
{
|
||||
var torrents = GetAffectedTorrentHashes(type);
|
||||
|
||||
await DialogService.InvokeDeleteTorrentDialog(ApiClient, [.. torrents]);
|
||||
}
|
||||
|
||||
private Dictionary<string, int> GetTags()
|
||||
{
|
||||
if (MainData is null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
return MainData.TagState.ToDictionary(d => d.Key, d => d.Value.Count);
|
||||
}
|
||||
|
||||
private Dictionary<string, int> GetCategories()
|
||||
{
|
||||
if (MainData is null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
return MainData.CategoriesState.ToDictionary(d => d.Key, d => d.Value.Count);
|
||||
}
|
||||
|
||||
private Dictionary<string, int> GetTrackers()
|
||||
{
|
||||
if (MainData is null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
return MainData.TrackersState
|
||||
.GroupBy(d => GetHostName(d.Key))
|
||||
.Select(l => new KeyValuePair<string, int>(GetHostName(l.First().Key), l.Sum(i => i.Value.Count)))
|
||||
.ToDictionary(d => d.Key, d => d.Value);
|
||||
}
|
||||
|
||||
private Dictionary<string, int> GetStatuses()
|
||||
{
|
||||
if (MainData is null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
return MainData.StatusState.ToDictionary(d => d.Key, d => d.Value.Count);
|
||||
}
|
||||
|
||||
private List<string> GetAffectedTorrentHashes(string type)
|
||||
{
|
||||
if (MainData is null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case _statusType:
|
||||
if (ContextMenuStatus is null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var status = Enum.Parse<Status>(ContextMenuStatus);
|
||||
|
||||
return MainData.Torrents.Where(t => FilterHelper.FilterStatus(t.Value, status)).Select(t => t.Value.Hash).ToList();
|
||||
|
||||
case _categoryType:
|
||||
if (ContextMenuCategory is null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
return MainData.Torrents.Where(t => FilterHelper.FilterCategory(t.Value, ContextMenuCategory, Preferences?.UseSubcategories ?? false)).Select(t => t.Value.Hash).ToList();
|
||||
|
||||
case _tagType:
|
||||
if (ContextMenuTag is null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
return MainData.Torrents.Where(t => FilterHelper.FilterTag(t.Value, ContextMenuTag)).Select(t => t.Value.Hash).ToList();
|
||||
|
||||
case _trackerType:
|
||||
if (ContextMenuTracker is null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
return MainData.Torrents.Where(t => FilterHelper.FilterTracker(t.Value, ContextMenuTracker)).Select(t => t.Value.Hash).ToList();
|
||||
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetHostName(string tracker)
|
||||
{
|
||||
try
|
||||
{
|
||||
var uri = new Uri(tracker);
|
||||
return uri.Host;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return tracker;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
101
Lantean.QBTMud/Components/GeneralTab.razor
Normal file
101
Lantean.QBTMud/Components/GeneralTab.razor
Normal file
@@ -0,0 +1,101 @@
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="details-tab-contents">
|
||||
<MudText Typo="Typo.subtitle2" Class="pt-6">Progress</MudText>
|
||||
<PieceProgress Pieces="Pieces" Hash="@Hash" />
|
||||
<MudText Typo="Typo.subtitle2" Class="pt-6">Transfer</MudText>
|
||||
<MudGrid>
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Time Active">@DisplayHelpers.Duration(Properties?.TimeElapsed)</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudField Label="ETA">@DisplayHelpers.Duration(Properties?.EstimatedTimeOfArrival)</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Connections">@DisplayHelpers.Duration(Properties?.Connections) @DisplayHelpers.EmptyIfNull(Properties?.ConnectionsLimit, "(", " max)")</MudField>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Downloaded">@DisplayHelpers.Size(Properties?.TotalDownloaded) @DisplayHelpers.Size(Properties?.TotalDownloadedSession, "(", " this session)")</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Uploaded">@DisplayHelpers.Size(Properties?.TotalUploaded) @DisplayHelpers.Size(Properties?.TotalUploaded, "(", " this session)")</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Seeds">@DisplayHelpers.Size(Properties?.Seeds) @DisplayHelpers.EmptyIfNull(Properties?.Seeds, "(", " total)")</MudField>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Download Speed">@DisplayHelpers.Speed(Properties?.DownloadSpeed) @DisplayHelpers.Speed(Properties?.DownloadSpeedAverage, "(", " avg.)")</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Upload Speed">@DisplayHelpers.Speed(Properties?.UploadSpeed) @DisplayHelpers.Speed(Properties?.UploadSpeedAverage, "(", " avg.)")</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Peers">@DisplayHelpers.EmptyIfNull(Properties?.Peers) @DisplayHelpers.EmptyIfNull(Properties?.Peers, "(", " total)")</MudField>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Download Limit">@DisplayHelpers.Speed(Properties?.DownloadLimit)</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Upload Limit">@DisplayHelpers.Speed(Properties?.UploadLimit)</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Wasted">@DisplayHelpers.Size(Properties?.TotalWasted)</MudField>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Share Ratio">@Properties?.ShareRatio.ToString("0.00")</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Reannounce In">@DisplayHelpers.Duration(Properties?.Reannounce)</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Last Seen Complete">@DisplayHelpers.DateTime(Properties?.LastSeen, "Never")</MudField>
|
||||
</MudItem>
|
||||
|
||||
</MudGrid>
|
||||
|
||||
<MudText Typo="Typo.subtitle2" Class="pt-6">Information</MudText>
|
||||
<MudGrid>
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Total Size">@DisplayHelpers.Size(Properties?.TotalSize)</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Pieces">
|
||||
@if (Properties is not null)
|
||||
{
|
||||
<text>@Properties.PiecesNum x @DisplayHelpers.Size(Properties.PieceSize) (have @Properties.PiecesHave)</text>
|
||||
}
|
||||
</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Created By">@Properties?.CreatedBy</MudField>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Added On">@DisplayHelpers.DateTime(Properties?.AdditionDate)</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Completed On">@DisplayHelpers.DateTime(Properties?.CompletionDate)</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Created On">@DisplayHelpers.DateTime(Properties?.CreationDate)</MudField>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12">
|
||||
<MudField Label="Info Hash v1">@Properties?.InfoHashV1</MudField>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12">
|
||||
<MudField Label="Info Hash v2">@Properties?.InfoHashV2</MudField>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12">
|
||||
<MudField Label="Save Path">@Properties?.SavePath</MudField>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12">
|
||||
<MudField Label="Comment">@Properties?.Comment</MudField>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudContainer>
|
||||
133
Lantean.QBTMud/Components/GeneralTab.razor.cs
Normal file
133
Lantean.QBTMud/Components/GeneralTab.razor.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBitTorrentClient.Models;
|
||||
using Lantean.QBTMud.Services;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using System.Net;
|
||||
|
||||
namespace Lantean.QBTMud.Components
|
||||
{
|
||||
public partial class GeneralTab : IAsyncDisposable
|
||||
{
|
||||
private readonly bool _refreshEnabled = true;
|
||||
|
||||
private readonly CancellationTokenSource _timerCancellationToken = new();
|
||||
private bool _disposedValue;
|
||||
|
||||
[Parameter, EditorRequired]
|
||||
public string? Hash { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool Active { get; set; }
|
||||
|
||||
[CascadingParameter(Name = "RefreshInterval")]
|
||||
public int RefreshInterval { get; set; }
|
||||
|
||||
[Inject]
|
||||
protected IApiClient ApiClient { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected IDataManager DataManager { get; set; } = default!;
|
||||
|
||||
protected IReadOnlyList<PieceState> Pieces { get; set; } = [];
|
||||
|
||||
protected TorrentProperties Properties { get; set; } = default!;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (Hash is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Active)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Properties = await ApiClient.GetTorrentProperties(Hash);
|
||||
}
|
||||
catch (HttpRequestException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Pieces = await ApiClient.GetTorrentPieceStates(Hash);
|
||||
}
|
||||
catch (HttpRequestException exception) when (exception.StatusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
Pieces = [];
|
||||
}
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (!_refreshEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (firstRender)
|
||||
{
|
||||
using (var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(RefreshInterval)))
|
||||
{
|
||||
while (!_timerCancellationToken.IsCancellationRequested && await timer.WaitForNextTickAsync())
|
||||
{
|
||||
if (Active && Hash is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Properties = await ApiClient.GetTorrentProperties(Hash);
|
||||
}
|
||||
catch (HttpRequestException exception) when (exception.StatusCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
_timerCancellationToken.CancelIfNotDisposed();
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Pieces = await ApiClient.GetTorrentPieceStates(Hash);
|
||||
}
|
||||
catch (HttpRequestException exception) when (exception.StatusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
Pieces = [];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual async Task DisposeAsync(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_timerCancellationToken.Cancel();
|
||||
_timerCancellationToken.Dispose();
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
await DisposeAsync(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
6
Lantean.QBTMud/Components/Menu.razor
Normal file
6
Lantean.QBTMud/Components/Menu.razor
Normal file
@@ -0,0 +1,6 @@
|
||||
@if (_isVisible)
|
||||
{
|
||||
<MudMenu Icon="@Icons.Material.Filled.MoreVert" Color="Color.Inherit" Dense="true" AnchorOrigin="Origin.BottomRight" TransformOrigin="Origin.TopLeft">
|
||||
<ApplicationActions IsMenu="true" Preferences="Preferences" />
|
||||
</MudMenu>
|
||||
}
|
||||
61
Lantean.QBTMud/Components/Menu.razor.cs
Normal file
61
Lantean.QBTMud/Components/Menu.razor.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBitTorrentClient.Models;
|
||||
using Lantean.QBTMud.Helpers;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMud.Components
|
||||
{
|
||||
public partial class Menu
|
||||
{
|
||||
private bool _isVisible = false;
|
||||
|
||||
private Preferences? _preferences;
|
||||
|
||||
[Inject]
|
||||
protected NavigationManager NavigationManager { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected IDialogService DialogService { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected IApiClient ApiClient { get; set; } = default!;
|
||||
|
||||
protected Preferences? Preferences => _preferences;
|
||||
|
||||
public void ShowMenu(Preferences? preferences = null)
|
||||
{
|
||||
_isVisible = true;
|
||||
_preferences = preferences;
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
protected async Task ResetWebUI()
|
||||
{
|
||||
var preferences = new UpdatePreferences
|
||||
{
|
||||
AlternativeWebuiEnabled = false,
|
||||
};
|
||||
|
||||
await ApiClient.SetApplicationPreferences(preferences);
|
||||
|
||||
NavigationManager.NavigateTo("/", true);
|
||||
}
|
||||
|
||||
protected async Task Logout()
|
||||
{
|
||||
await DialogService.ShowConfirmDialog("Logout?", "Are you sure you want to logout?", async () =>
|
||||
{
|
||||
await ApiClient.Logout();
|
||||
|
||||
NavigationManager.NavigateTo("/login", true);
|
||||
});
|
||||
}
|
||||
|
||||
protected async Task Exit()
|
||||
{
|
||||
await DialogService.ShowConfirmDialog("Quit?", "Are you sure you want to exit qBittorrent?", ApiClient.Shutdown);
|
||||
}
|
||||
}
|
||||
}
|
||||
243
Lantean.QBTMud/Components/Options/AdvancedOptions.razor
Normal file
243
Lantean.QBTMud/Components/Options/AdvancedOptions.razor
Normal file
@@ -0,0 +1,243 @@
|
||||
@inherits Options
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4 mt-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.subtitle2">qBittorrent Section</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="string" Label="Resume data storage type (requires restart)" Value="ResumeDataStorageType" ValueChanged="ResumeDataStorageTypeChanged" Variant="Variant.Outlined">
|
||||
<MudSelectItem T="string" Value="@("Legacy")">Fastresume files</MudSelectItem>
|
||||
<MudSelectItem T="string" Value="@("SQLite")">SQLite database (experimental)</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Physical memory (RAM) usage limit (applied if libtorrent >= 2.0)" Value="MemoryWorkingSetLimit" ValueChanged="MemoryWorkingSetLimitChanged" Min="0" HelperText="This option is less effective on Linux" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="MiB" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="string" Label="Network interface" Value="CurrentNetworkInterface" ValueChanged="CurrentNetworkInterfaceChanged" Variant="Variant.Outlined">
|
||||
<MudSelectItem T="string" Value="@("")">Any interface</MudSelectItem>
|
||||
@foreach (var networkInterface in NetworkInterfaces)
|
||||
{
|
||||
<MudSelectItem T="string" Value="networkInterface.Value">@networkInterface.Name</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="string" Label="Optional IP address to bind to" Value="CurrentInterfaceAddress" ValueChanged="CurrentInterfaceAddressChanged" Variant="Variant.Outlined">
|
||||
<MudSelectItem T="string" Value="@("")">All addresses</MudSelectItem>
|
||||
<MudSelectItem T="string" Value="@("0.0.0.0")">All IPv4 addresses</MudSelectItem>
|
||||
<MudSelectItem T="string" Value="@("::")">All IPv6 addresses</MudSelectItem>
|
||||
@foreach (var address in NetworkInterfaceAddresses)
|
||||
{
|
||||
<MudSelectItem T="string" Value="address">@address</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Save resume data interval" Value="SaveResumeDataInterval" ValueChanged="SaveResumeDataIntervalChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="min" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label=".torrent file size limit" Value="TorrentFileSizeLimit" ValueChanged="TorrentFileSizeLimitChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="MiB" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Recheck torrents on completion" Value="RecheckCompletedTorrents" ValueChanged="RecheckCompletedTorrentsChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Refresh interval" Value="RefreshInterval" ValueChanged="RefreshIntervalChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="ms" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Resolve peer countries" Value="ResolvePeerCountries" ValueChanged="ResolvePeerCountriesChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Reannounce to all trackers when IP or port changed" Value="ReannounceWhenAddressChanged" ValueChanged="ReannounceWhenAddressChangedChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Enable embedded tracker" Value="EnableEmbeddedTracker" ValueChanged="EnableEmbeddedTrackerChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Embedded tracker port" Value="EmbeddedTrackerPort" ValueChanged="EmbeddedTrackerPortChanged" Min="@Options.MinPortValue" Max="@Options.MaxPortValue" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Enable port forwarding for embedded tracker" Value="EmbeddedTrackerPortForwarding" ValueChanged="EmbeddedTrackerPortForwardingChanged" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4 mt-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.subtitle2">qBittorrent Section</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Bdecode depth limit" Value="BdecodeDepthLimit" ValueChanged="BdecodeDepthLimitChanged" Min="0" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Bdecode token limit" Value="BdecodeTokenLimit" ValueChanged="BdecodeTokenLimitChanged" Min="0" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Asynchronous I/O threads" Value="AsyncIoThreads" ValueChanged="AsyncIoThreadsChanged" Min="0" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Hashing threads (requires libtorrent >= 2.0)" Value="HashingThreads" ValueChanged="HashingThreadsChanged" Min="0" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="File pool size" Value="FilePoolSize" ValueChanged="FilePoolSizeChanged" Min="0" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Outstanding memory when checking torrents" Value="CheckingMemoryUse" ValueChanged="CheckingMemoryUseChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="MiB" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Disk cache (requires libtorrent < 2.0)" Value="DiskCache" ValueChanged="DiskCacheChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="MiB" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Disk cache expiry interval (requires libtorrent < 2.0)" Value="DiskCacheTtl" ValueChanged="DiskCacheTtlChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="s" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Disk queue size" Value="DiskQueueSize" ValueChanged="DiskQueueSizeChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="int" Label="Disk IO type (libtorrent >= 2.0; requires restart)" Value="DiskIoType" ValueChanged="DiskIoTypeChanged" Variant="Variant.Outlined">
|
||||
<MudSelectItem T="int" Value="0">Default</MudSelectItem>
|
||||
<MudSelectItem T="int" Value="1">Memory mapped files</MudSelectItem>
|
||||
<MudSelectItem T="int" Value="2">POSIX-compliant</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="int" Label="Disk IO read mode" Value="DiskIoReadMode" ValueChanged="DiskIoReadModeChanged" Variant="Variant.Outlined">
|
||||
<MudSelectItem T="int" Value="0">Disable OS cache</MudSelectItem>
|
||||
<MudSelectItem T="int" Value="1">Enable OS cache</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="int" Label="Disk IO write mode" Value="DiskIoWriteMode" ValueChanged="DiskIoWriteModeChanged" Variant="Variant.Outlined">
|
||||
<MudSelectItem T="int" Value="0">Disable OS cache</MudSelectItem>
|
||||
<MudSelectItem T="int" Value="1">Enable OS cache</MudSelectItem>
|
||||
<MudSelectItem T="int" Value="2">Write-through (requires libtorrent >= 2.0.6)</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Coalesce reads & writes (requires libtorrent < 2.0)" Value="EnableCoalesceReadWrite" ValueChanged="EnableCoalesceReadWriteChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Use piece extent affinity" Value="EnablePieceExtentAffinity" ValueChanged="EnablePieceExtentAffinityChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Send upload piece suggestions" Value="EnableUploadSuggestions" ValueChanged="EnableUploadSuggestionsChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Send buffer watermark" Value="SendBufferWatermark" ValueChanged="SendBufferWatermarkChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Send buffer low watermark" Value="SendBufferLowWatermark" ValueChanged="SendBufferLowWatermarkChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Send buffer watermark factor" Value="SendBufferWatermarkFactor" ValueChanged="SendBufferWatermarkFactorChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="%" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Outgoing connections per second" Value="ConnectionSpeed" ValueChanged="ConnectionSpeedChanged" Min="0" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Socket send buffer size [0: system default]" Value="SocketSendBufferSize" ValueChanged="SocketSendBufferSizeChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Socket receive buffer size [0: system default]" Value="SocketReceiveBufferSize" ValueChanged="SocketReceiveBufferSizeChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Socket backlog size" Value="SocketBacklogSize" ValueChanged="SocketBacklogSizeChanged" Min="0" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Outgoing ports (Min) [0: disabled]" Value="OutgoingPortsMin" ValueChanged="OutgoingPortsMinChanged" Min="0" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Outgoing ports (Max) [0: disabled]" Value="OutgoingPortsMax" ValueChanged="OutgoingPortsMaxChanged" Min="0" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="UPnP lease duration [0: permanent lease]" Value="UpnpLeaseDuration" ValueChanged="UpnpLeaseDurationChanged" Min="0" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Type of service (ToS) for connections to peers" Value="PeerTos" ValueChanged="PeerTosChanged" Min="0" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="int" Label="μTP-TCP mixed mode algorithm" Value="UtpTcpMixedMode" ValueChanged="UtpTcpMixedModeChanged" Variant="Variant.Outlined">
|
||||
<MudSelectItem T="int" Value="0">Prefer TCP</MudSelectItem>
|
||||
<MudSelectItem T="int" Value="1">Peer proportional (throttles TCP)</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Support internationalized domain name (IDN)" Value="IdnSupportEnabled" ValueChanged="IdnSupportEnabledChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Allow multiple connections from the same IP address" Value="EnableMultiConnectionsFromSameIp" ValueChanged="EnableMultiConnectionsFromSameIpChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Validate HTTPS tracker certificate" Value="ValidateHttpsTrackerCertificate" ValueChanged="ValidateHttpsTrackerCertificateChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Server-side request forgery (SSRF) mitigation" Value="SsrfMitigation" ValueChanged="SsrfMitigationChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Disallow connection to peers on privileged ports" Value="BlockPeersOnPrivilegedPorts" ValueChanged="BlockPeersOnPrivilegedPortsChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="int" Label="Upload slots behavior" Value="UploadSlotsBehavior" ValueChanged="UploadSlotsBehaviorChanged" Variant="Variant.Outlined">
|
||||
<MudSelectItem T="int" Value="0">Fixed slots</MudSelectItem>
|
||||
<MudSelectItem T="int" Value="1">Upload rate based</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="int" Label="Upload choking algorithm" Value="UploadChokingAlgorithm" ValueChanged="UploadChokingAlgorithmChanged" Variant="Variant.Outlined">
|
||||
<MudSelectItem T="int" Value="0">Round-robin</MudSelectItem>
|
||||
<MudSelectItem T="int" Value="1">Fastest upload</MudSelectItem>
|
||||
<MudSelectItem T="int" Value="2">Anti-leech</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Always announce to all trackers in a tier" Value="AnnounceToAllTrackers" ValueChanged="AnnounceToAllTrackersChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Always announce to all tiers" Value="AnnounceToAllTiers" ValueChanged="AnnounceToAllTiersChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="IP address reported to trackers (requires restart)" Value="AnnounceIp" ValueChanged="AnnounceIpChanged" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Max concurrent HTTP announces" Value="MaxConcurrentHttpAnnounces" ValueChanged="MaxConcurrentHttpAnnouncesChanged" Min="0" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Stop tracker timeout [0: disabled]" Value="StopTrackerTimeout" ValueChanged="StopTrackerTimeoutChanged" Min="0" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Peer turnover disconnect percentage:" Value="PeerTurnover" ValueChanged="PeerTurnoverChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="%" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Peer turnover threshold percentage" Value="PeerTurnoverCutoff" ValueChanged="PeerTurnoverCutoffChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="%" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Peer turnover disconnect interval" Value="PeerTurnoverInterval" ValueChanged="PeerTurnoverIntervalChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="s" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Maximum outstanding requests to a single peer" Value="RequestQueueSize" ValueChanged="RequestQueueSizeChanged" Min="0" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="I2P inbound quantity (requires libtorrent >= 2.0)" Value="I2pInboundQuantity" ValueChanged="I2pInboundQuantityChanged" Min="0" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="I2P outbound quantity (requires libtorrent >= 2.0)" Value="I2pOutboundQuantity" ValueChanged="I2pOutboundQuantityChanged" Min="0" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="I2P inbound length (requires libtorrent >= 2.0)" Value="I2pInboundLength" ValueChanged="I2pInboundLengthChanged" Min="0" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="I2P outbound length (requires libtorrent >= 2.0)" Value="I2pOutboundLength" ValueChanged="I2pOutboundLengthChanged" Min="0" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
611
Lantean.QBTMud/Components/Options/AdvancedOptions.razor.cs
Normal file
611
Lantean.QBTMud/Components/Options/AdvancedOptions.razor.cs
Normal file
@@ -0,0 +1,611 @@
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBitTorrentClient.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Lantean.QBTMud.Components.Options
|
||||
{
|
||||
public partial class AdvancedOptions : Options
|
||||
{
|
||||
[Inject]
|
||||
public IApiClient ApiClient { get; set; } = default!;
|
||||
|
||||
protected string? ResumeDataStorageType { get; private set; }
|
||||
protected int MemoryWorkingSetLimit { get; private set; }
|
||||
protected string? CurrentNetworkInterface { get; private set; }
|
||||
protected string? CurrentInterfaceAddress { get; private set; }
|
||||
protected int SaveResumeDataInterval { get; private set; }
|
||||
protected int TorrentFileSizeLimit { get; private set; }
|
||||
protected bool RecheckCompletedTorrents { get; private set; }
|
||||
protected string? AppInstanceName { get; private set; }
|
||||
protected int RefreshInterval { get; private set; }
|
||||
protected bool ResolvePeerCountries { get; private set; }
|
||||
protected bool ReannounceWhenAddressChanged { get; private set; }
|
||||
protected int BdecodeDepthLimit { get; private set; }
|
||||
protected int BdecodeTokenLimit { get; private set; }
|
||||
protected int AsyncIoThreads { get; private set; }
|
||||
protected int HashingThreads { get; private set; }
|
||||
protected int FilePoolSize { get; private set; }
|
||||
protected int CheckingMemoryUse { get; private set; }
|
||||
protected int DiskCache { get; private set; }
|
||||
protected int DiskCacheTtl { get; private set; }
|
||||
protected int DiskQueueSize { get; private set; }
|
||||
protected int DiskIoType { get; private set; }
|
||||
protected int DiskIoReadMode { get; private set; }
|
||||
protected int DiskIoWriteMode { get; private set; }
|
||||
protected bool EnableCoalesceReadWrite { get; private set; }
|
||||
protected bool EnablePieceExtentAffinity { get; private set; }
|
||||
protected bool EnableUploadSuggestions { get; private set; }
|
||||
protected int SendBufferWatermark { get; private set; }
|
||||
protected int SendBufferLowWatermark { get; private set; }
|
||||
protected int SendBufferWatermarkFactor { get; private set; }
|
||||
protected int ConnectionSpeed { get; private set; }
|
||||
protected int SocketSendBufferSize { get; private set; }
|
||||
protected int SocketReceiveBufferSize { get; private set; }
|
||||
protected int SocketBacklogSize { get; private set; }
|
||||
protected int OutgoingPortsMin { get; private set; }
|
||||
protected int OutgoingPortsMax { get; private set; }
|
||||
protected int UpnpLeaseDuration { get; private set; }
|
||||
protected int PeerTos { get; private set; }
|
||||
protected int UtpTcpMixedMode { get; private set; }
|
||||
protected bool IdnSupportEnabled { get; private set; }
|
||||
protected bool EnableMultiConnectionsFromSameIp { get; private set; }
|
||||
protected bool ValidateHttpsTrackerCertificate { get; private set; }
|
||||
protected bool SsrfMitigation { get; private set; }
|
||||
protected bool BlockPeersOnPrivilegedPorts { get; private set; }
|
||||
protected bool EnableEmbeddedTracker { get; private set; }
|
||||
protected int EmbeddedTrackerPort { get; private set; }
|
||||
protected bool EmbeddedTrackerPortForwarding { get; private set; }
|
||||
protected bool MarkOfTheWeb { get; private set; }
|
||||
protected string? PythonExecutablePath { get; private set; }
|
||||
protected int UploadSlotsBehavior { get; private set; }
|
||||
protected int UploadChokingAlgorithm { get; private set; }
|
||||
protected bool AnnounceToAllTrackers { get; private set; }
|
||||
protected bool AnnounceToAllTiers { get; private set; }
|
||||
protected string? AnnounceIp { get; private set; }
|
||||
protected int MaxConcurrentHttpAnnounces { get; private set; }
|
||||
protected int StopTrackerTimeout { get; private set; }
|
||||
protected int PeerTurnover { get; private set; }
|
||||
protected int PeerTurnoverCutoff { get; private set; }
|
||||
protected int PeerTurnoverInterval { get; private set; }
|
||||
protected int RequestQueueSize { get; private set; }
|
||||
protected string? DhtBootstrapNodes { get; private set; }
|
||||
protected int I2pInboundQuantity { get; private set; }
|
||||
protected int I2pOutboundQuantity { get; private set; }
|
||||
protected int I2pInboundLength { get; private set; }
|
||||
protected int I2pOutboundLength { get; private set; }
|
||||
|
||||
protected IReadOnlyList<NetworkInterface> NetworkInterfaces { get; private set; } = [];
|
||||
|
||||
protected IReadOnlyList<string> NetworkInterfaceAddresses { get; private set; } = [];
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
NetworkInterfaces = await ApiClient.GetNetworkInterfaces();
|
||||
}
|
||||
|
||||
protected override bool SetOptions()
|
||||
{
|
||||
if (Preferences is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ResumeDataStorageType = Preferences.ResumeDataStorageType;
|
||||
MemoryWorkingSetLimit = Preferences.MemoryWorkingSetLimit;
|
||||
CurrentNetworkInterface = Preferences.CurrentNetworkInterface;
|
||||
CurrentInterfaceAddress = Preferences.CurrentInterfaceAddress;
|
||||
SaveResumeDataInterval = Preferences.SaveResumeDataInterval;
|
||||
TorrentFileSizeLimit = Preferences.TorrentFileSizeLimit / 1024 / 1024;
|
||||
RecheckCompletedTorrents = Preferences.RecheckCompletedTorrents;
|
||||
AppInstanceName = Preferences.AppInstanceName;
|
||||
RefreshInterval = Preferences.RefreshInterval;
|
||||
ResolvePeerCountries = Preferences.ResolvePeerCountries;
|
||||
ReannounceWhenAddressChanged = Preferences.ReannounceWhenAddressChanged;
|
||||
BdecodeDepthLimit = Preferences.BdecodeDepthLimit;
|
||||
BdecodeTokenLimit = Preferences.BdecodeTokenLimit;
|
||||
AsyncIoThreads = Preferences.AsyncIoThreads;
|
||||
HashingThreads = Preferences.HashingThreads;
|
||||
FilePoolSize = Preferences.FilePoolSize;
|
||||
CheckingMemoryUse = Preferences.CheckingMemoryUse;
|
||||
DiskCache = Preferences.DiskCache;
|
||||
DiskCacheTtl = Preferences.DiskCacheTtl;
|
||||
DiskQueueSize = Preferences.DiskQueueSize / 1024;
|
||||
DiskIoType = Preferences.DiskIoType;
|
||||
DiskIoReadMode = Preferences.DiskIoReadMode;
|
||||
DiskIoWriteMode = Preferences.DiskIoWriteMode;
|
||||
EnableCoalesceReadWrite = Preferences.EnableCoalesceReadWrite;
|
||||
EnablePieceExtentAffinity = Preferences.EnablePieceExtentAffinity;
|
||||
EnableUploadSuggestions = Preferences.EnableUploadSuggestions;
|
||||
SendBufferWatermark = Preferences.SendBufferWatermark;
|
||||
SendBufferLowWatermark = Preferences.SendBufferLowWatermark;
|
||||
SendBufferWatermarkFactor = Preferences.SendBufferWatermarkFactor;
|
||||
ConnectionSpeed = Preferences.ConnectionSpeed;
|
||||
SocketSendBufferSize = Preferences.SocketSendBufferSize / 1024;
|
||||
SocketReceiveBufferSize = Preferences.SocketReceiveBufferSize / 1024;
|
||||
SocketBacklogSize = Preferences.SocketBacklogSize;
|
||||
OutgoingPortsMin = Preferences.OutgoingPortsMin;
|
||||
OutgoingPortsMax = Preferences.OutgoingPortsMax;
|
||||
UpnpLeaseDuration = Preferences.UpnpLeaseDuration;
|
||||
PeerTos = Preferences.PeerTos;
|
||||
UtpTcpMixedMode = Preferences.UtpTcpMixedMode;
|
||||
IdnSupportEnabled = Preferences.IdnSupportEnabled;
|
||||
EnableMultiConnectionsFromSameIp = Preferences.EnableMultiConnectionsFromSameIp;
|
||||
ValidateHttpsTrackerCertificate = Preferences.ValidateHttpsTrackerCertificate;
|
||||
SsrfMitigation = Preferences.SsrfMitigation;
|
||||
BlockPeersOnPrivilegedPorts = Preferences.BlockPeersOnPrivilegedPorts;
|
||||
EnableEmbeddedTracker = Preferences.EnableEmbeddedTracker;
|
||||
EmbeddedTrackerPort = Preferences.EmbeddedTrackerPort;
|
||||
EmbeddedTrackerPortForwarding = Preferences.EmbeddedTrackerPortForwarding;
|
||||
MarkOfTheWeb = Preferences.MarkOfTheWeb;
|
||||
PythonExecutablePath = Preferences.PythonExecutablePath;
|
||||
UploadSlotsBehavior = Preferences.UploadSlotsBehavior;
|
||||
UploadChokingAlgorithm = Preferences.UploadChokingAlgorithm;
|
||||
AnnounceToAllTrackers = Preferences.AnnounceToAllTrackers;
|
||||
AnnounceToAllTiers = Preferences.AnnounceToAllTiers;
|
||||
AnnounceIp = Preferences.AnnounceIp;
|
||||
MaxConcurrentHttpAnnounces = Preferences.MaxConcurrentHttpAnnounces;
|
||||
StopTrackerTimeout = Preferences.StopTrackerTimeout;
|
||||
PeerTurnover = Preferences.PeerTurnover;
|
||||
PeerTurnoverCutoff = Preferences.PeerTurnoverCutoff;
|
||||
PeerTurnoverInterval = Preferences.PeerTurnoverInterval;
|
||||
RequestQueueSize = Preferences.RequestQueueSize;
|
||||
DhtBootstrapNodes = Preferences.DhtBootstrapNodes;
|
||||
I2pInboundQuantity = Preferences.I2pInboundQuantity;
|
||||
I2pOutboundQuantity = Preferences.I2pOutboundQuantity;
|
||||
I2pInboundLength = Preferences.I2pInboundLength;
|
||||
I2pOutboundLength = Preferences.I2pOutboundLength;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async Task ResumeDataStorageTypeChanged(string value)
|
||||
{
|
||||
ResumeDataStorageType = value;
|
||||
UpdatePreferences.ResumeDataStorageType = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task MemoryWorkingSetLimitChanged(int value)
|
||||
{
|
||||
MemoryWorkingSetLimit = value;
|
||||
UpdatePreferences.MemoryWorkingSetLimit = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task CurrentNetworkInterfaceChanged(string value)
|
||||
{
|
||||
CurrentNetworkInterface = value;
|
||||
UpdatePreferences.CurrentNetworkInterface = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
|
||||
NetworkInterfaceAddresses = await ApiClient.GetNetworkInterfaceAddressList(value);
|
||||
}
|
||||
|
||||
protected async Task CurrentInterfaceAddressChanged(string value)
|
||||
{
|
||||
CurrentInterfaceAddress = value;
|
||||
UpdatePreferences.CurrentInterfaceAddress = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task SaveResumeDataIntervalChanged(int value)
|
||||
{
|
||||
SaveResumeDataInterval = value;
|
||||
UpdatePreferences.SaveResumeDataInterval = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task TorrentFileSizeLimitChanged(int value)
|
||||
{
|
||||
TorrentFileSizeLimit = value;
|
||||
UpdatePreferences.TorrentFileSizeLimit = value * 1024 * 1024;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task RecheckCompletedTorrentsChanged(bool value)
|
||||
{
|
||||
RecheckCompletedTorrents = value;
|
||||
UpdatePreferences.RecheckCompletedTorrents = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task AppInstanceNameChanged(string value)
|
||||
{
|
||||
AppInstanceName = value;
|
||||
UpdatePreferences.AppInstanceName = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task RefreshIntervalChanged(int value)
|
||||
{
|
||||
RefreshInterval = value;
|
||||
UpdatePreferences.RefreshInterval = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task ResolvePeerCountriesChanged(bool value)
|
||||
{
|
||||
ResolvePeerCountries = value;
|
||||
UpdatePreferences.ResolvePeerCountries = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task ReannounceWhenAddressChangedChanged(bool value)
|
||||
{
|
||||
ReannounceWhenAddressChanged = value;
|
||||
UpdatePreferences.ReannounceWhenAddressChanged = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task BdecodeDepthLimitChanged(int value)
|
||||
{
|
||||
BdecodeDepthLimit = value;
|
||||
UpdatePreferences.BdecodeDepthLimit = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task BdecodeTokenLimitChanged(int value)
|
||||
{
|
||||
BdecodeTokenLimit = value;
|
||||
UpdatePreferences.BdecodeTokenLimit = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task AsyncIoThreadsChanged(int value)
|
||||
{
|
||||
AsyncIoThreads = value;
|
||||
UpdatePreferences.AsyncIoThreads = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task HashingThreadsChanged(int value)
|
||||
{
|
||||
HashingThreads = value;
|
||||
UpdatePreferences.HashingThreads = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task FilePoolSizeChanged(int value)
|
||||
{
|
||||
FilePoolSize = value;
|
||||
UpdatePreferences.FilePoolSize = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task CheckingMemoryUseChanged(int value)
|
||||
{
|
||||
CheckingMemoryUse = value;
|
||||
UpdatePreferences.CheckingMemoryUse = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task DiskCacheChanged(int value)
|
||||
{
|
||||
DiskCache = value;
|
||||
UpdatePreferences.DiskCache = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task DiskCacheTtlChanged(int value)
|
||||
{
|
||||
DiskCacheTtl = value;
|
||||
UpdatePreferences.DiskCacheTtl = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task DiskQueueSizeChanged(int value)
|
||||
{
|
||||
DiskQueueSize = value;
|
||||
UpdatePreferences.DiskQueueSize = value * 1024;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task DiskIoTypeChanged(int value)
|
||||
{
|
||||
DiskIoType = value;
|
||||
UpdatePreferences.DiskIoType = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task DiskIoReadModeChanged(int value)
|
||||
{
|
||||
DiskIoReadMode = value;
|
||||
UpdatePreferences.DiskIoReadMode = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task DiskIoWriteModeChanged(int value)
|
||||
{
|
||||
DiskIoWriteMode = value;
|
||||
UpdatePreferences.DiskIoWriteMode = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task EnableCoalesceReadWriteChanged(bool value)
|
||||
{
|
||||
EnableCoalesceReadWrite = value;
|
||||
UpdatePreferences.EnableCoalesceReadWrite = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task EnablePieceExtentAffinityChanged(bool value)
|
||||
{
|
||||
EnablePieceExtentAffinity = value;
|
||||
UpdatePreferences.EnablePieceExtentAffinity = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task EnableUploadSuggestionsChanged(bool value)
|
||||
{
|
||||
EnableUploadSuggestions = value;
|
||||
UpdatePreferences.EnableUploadSuggestions = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task SendBufferWatermarkChanged(int value)
|
||||
{
|
||||
SendBufferWatermark = value;
|
||||
UpdatePreferences.SendBufferWatermark = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task SendBufferLowWatermarkChanged(int value)
|
||||
{
|
||||
SendBufferLowWatermark = value;
|
||||
UpdatePreferences.SendBufferLowWatermark = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task SendBufferWatermarkFactorChanged(int value)
|
||||
{
|
||||
SendBufferWatermarkFactor = value;
|
||||
UpdatePreferences.SendBufferWatermarkFactor = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task ConnectionSpeedChanged(int value)
|
||||
{
|
||||
ConnectionSpeed = value;
|
||||
UpdatePreferences.ConnectionSpeed = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task SocketSendBufferSizeChanged(int value)
|
||||
{
|
||||
SocketSendBufferSize = value;
|
||||
UpdatePreferences.SocketSendBufferSize = value * 1024;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task SocketReceiveBufferSizeChanged(int value)
|
||||
{
|
||||
SocketReceiveBufferSize = value;
|
||||
UpdatePreferences.SocketReceiveBufferSize = value * 1024;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task SocketBacklogSizeChanged(int value)
|
||||
{
|
||||
SocketBacklogSize = value;
|
||||
UpdatePreferences.SocketBacklogSize = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task OutgoingPortsMinChanged(int value)
|
||||
{
|
||||
OutgoingPortsMin = value;
|
||||
UpdatePreferences.OutgoingPortsMin = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task OutgoingPortsMaxChanged(int value)
|
||||
{
|
||||
OutgoingPortsMax = value;
|
||||
UpdatePreferences.OutgoingPortsMax = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task UpnpLeaseDurationChanged(int value)
|
||||
{
|
||||
UpnpLeaseDuration = value;
|
||||
UpdatePreferences.UpnpLeaseDuration = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task PeerTosChanged(int value)
|
||||
{
|
||||
PeerTos = value;
|
||||
UpdatePreferences.PeerTos = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task UtpTcpMixedModeChanged(int value)
|
||||
{
|
||||
UtpTcpMixedMode = value;
|
||||
UpdatePreferences.UtpTcpMixedMode = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task IdnSupportEnabledChanged(bool value)
|
||||
{
|
||||
IdnSupportEnabled = value;
|
||||
UpdatePreferences.IdnSupportEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task EnableMultiConnectionsFromSameIpChanged(bool value)
|
||||
{
|
||||
EnableMultiConnectionsFromSameIp = value;
|
||||
UpdatePreferences.EnableMultiConnectionsFromSameIp = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task ValidateHttpsTrackerCertificateChanged(bool value)
|
||||
{
|
||||
ValidateHttpsTrackerCertificate = value;
|
||||
UpdatePreferences.ValidateHttpsTrackerCertificate = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task SsrfMitigationChanged(bool value)
|
||||
{
|
||||
SsrfMitigation = value;
|
||||
UpdatePreferences.SsrfMitigation = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task BlockPeersOnPrivilegedPortsChanged(bool value)
|
||||
{
|
||||
BlockPeersOnPrivilegedPorts = value;
|
||||
UpdatePreferences.BlockPeersOnPrivilegedPorts = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task EnableEmbeddedTrackerChanged(bool value)
|
||||
{
|
||||
EnableEmbeddedTracker = value;
|
||||
UpdatePreferences.EnableEmbeddedTracker = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task EmbeddedTrackerPortChanged(int value)
|
||||
{
|
||||
EmbeddedTrackerPort = value;
|
||||
UpdatePreferences.EmbeddedTrackerPort = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task EmbeddedTrackerPortForwardingChanged(bool value)
|
||||
{
|
||||
EmbeddedTrackerPortForwarding = value;
|
||||
UpdatePreferences.EmbeddedTrackerPortForwarding = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task MarkOfTheWebChanged(bool value)
|
||||
{
|
||||
MarkOfTheWeb = value;
|
||||
UpdatePreferences.MarkOfTheWeb = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task PythonExecutablePathChanged(string value)
|
||||
{
|
||||
PythonExecutablePath = value;
|
||||
UpdatePreferences.PythonExecutablePath = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task UploadSlotsBehaviorChanged(int value)
|
||||
{
|
||||
UploadSlotsBehavior = value;
|
||||
UpdatePreferences.UploadSlotsBehavior = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task UploadChokingAlgorithmChanged(int value)
|
||||
{
|
||||
UploadChokingAlgorithm = value;
|
||||
UpdatePreferences.UploadChokingAlgorithm = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task AnnounceToAllTrackersChanged(bool value)
|
||||
{
|
||||
AnnounceToAllTrackers = value;
|
||||
UpdatePreferences.AnnounceToAllTrackers = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task AnnounceToAllTiersChanged(bool value)
|
||||
{
|
||||
AnnounceToAllTiers = value;
|
||||
UpdatePreferences.AnnounceToAllTiers = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task AnnounceIpChanged(string value)
|
||||
{
|
||||
AnnounceIp = value;
|
||||
UpdatePreferences.AnnounceIp = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task MaxConcurrentHttpAnnouncesChanged(int value)
|
||||
{
|
||||
MaxConcurrentHttpAnnounces = value;
|
||||
UpdatePreferences.MaxConcurrentHttpAnnounces = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task StopTrackerTimeoutChanged(int value)
|
||||
{
|
||||
StopTrackerTimeout = value;
|
||||
UpdatePreferences.StopTrackerTimeout = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task PeerTurnoverChanged(int value)
|
||||
{
|
||||
PeerTurnover = value;
|
||||
UpdatePreferences.PeerTurnover = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task PeerTurnoverCutoffChanged(int value)
|
||||
{
|
||||
PeerTurnoverCutoff = value;
|
||||
UpdatePreferences.PeerTurnoverCutoff = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task PeerTurnoverIntervalChanged(int value)
|
||||
{
|
||||
PeerTurnoverInterval = value;
|
||||
UpdatePreferences.PeerTurnoverInterval = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task RequestQueueSizeChanged(int value)
|
||||
{
|
||||
RequestQueueSize = value;
|
||||
UpdatePreferences.RequestQueueSize = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task DhtBootstrapNodesChanged(string value)
|
||||
{
|
||||
DhtBootstrapNodes = value;
|
||||
UpdatePreferences.DhtBootstrapNodes = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task I2pInboundQuantityChanged(int value)
|
||||
{
|
||||
I2pInboundQuantity = value;
|
||||
UpdatePreferences.I2pInboundQuantity = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task I2pOutboundQuantityChanged(int value)
|
||||
{
|
||||
I2pOutboundQuantity = value;
|
||||
UpdatePreferences.I2pOutboundQuantity = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task I2pInboundLengthChanged(int value)
|
||||
{
|
||||
I2pInboundLength = value;
|
||||
UpdatePreferences.I2pInboundLength = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task I2pOutboundLengthChanged(int value)
|
||||
{
|
||||
I2pOutboundLength = value;
|
||||
UpdatePreferences.I2pOutboundLength = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
}
|
||||
}
|
||||
74
Lantean.QBTMud/Components/Options/BehaviourOptions.razor
Normal file
74
Lantean.QBTMud/Components/Options/BehaviourOptions.razor
Normal file
@@ -0,0 +1,74 @@
|
||||
@inherits Options
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4 mt-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.subtitle2">Language</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="string" Label="User Interface Language" Value="@("en-US")" Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="@("en-US")">English</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.subtitle2">Log File</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Log file" Value="FileLogEnabled" ValueChanged="FileLogEnabledChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="Save Path" Value="FileLogPath" ValueChanged="FileLogPathChanged" Disabled="@(!FileLogEnabled)" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="3">
|
||||
<FieldSwitch Label="Backup the log after" Value="FileLogBackupEnabled" ValueChanged="FileLogBackupEnabledChanged" Disabled="@(!FileLogEnabled)" />
|
||||
</MudItem>
|
||||
<MudItem xs="9">
|
||||
<MudNumericField T="int" Value="FileLogMaxSize" ValueChanged="FileLogMaxSizeChanged" Disabled="@(!FileLogEnabled)" Min="1" Max="1024000" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB" />
|
||||
</MudItem>
|
||||
<MudItem xs="3">
|
||||
<FieldSwitch Label="Delete backups older than" Value="FileLogDeleteOld" ValueChanged="FileLogDeleteOldChanged" Disabled="@(!FileLogEnabled)" />
|
||||
</MudItem>
|
||||
<MudItem xs="9">
|
||||
<MudGrid>
|
||||
<MudItem xs="9">
|
||||
<MudNumericField T="int" Value="FileLogAge" ValueChanged="FileLogAgeChanged" Disabled="@(!FileLogEnabled)" Min="1" Max="365" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="3">
|
||||
<MudSelect T="int" Value="FileLogAgeType" ValueChanged="FileLogAgeTypeChanged" Disabled="@(!FileLogEnabled)" Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="0">days</MudSelectItem>
|
||||
<MudSelectItem Value="1">months</MudSelectItem>
|
||||
<MudSelectItem Value="2">years</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.subtitle2">Performance</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Log performance warnings" Value="PerformanceWarning" ValueChanged="PerformanceWarningChanged" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
99
Lantean.QBTMud/Components/Options/BehaviourOptions.razor.cs
Normal file
99
Lantean.QBTMud/Components/Options/BehaviourOptions.razor.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using Lantean.QBitTorrentClient.Models;
|
||||
|
||||
namespace Lantean.QBTMud.Components.Options
|
||||
{
|
||||
public partial class BehaviourOptions : Options
|
||||
{
|
||||
protected bool FileLogEnabled { get; set; }
|
||||
|
||||
protected string? FileLogPath { get; set; }
|
||||
|
||||
protected bool FileLogBackupEnabled { get; set; }
|
||||
|
||||
protected int FileLogMaxSize { get; set; }
|
||||
|
||||
protected bool FileLogDeleteOld { get; set; }
|
||||
|
||||
protected int FileLogAge { get; set; }
|
||||
|
||||
protected int FileLogAgeType { get; set; }
|
||||
|
||||
protected bool PerformanceWarning { get; set; }
|
||||
|
||||
protected override bool SetOptions()
|
||||
{
|
||||
if (Preferences is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
FileLogEnabled = Preferences.FileLogEnabled;
|
||||
FileLogPath = Preferences.FileLogPath;
|
||||
FileLogBackupEnabled = Preferences.FileLogBackupEnabled;
|
||||
FileLogMaxSize = Preferences.FileLogMaxSize;
|
||||
FileLogDeleteOld = Preferences.FileLogDeleteOld;
|
||||
FileLogAge = Preferences.FileLogAge;
|
||||
FileLogAgeType = Preferences.FileLogAgeType;
|
||||
PerformanceWarning = Preferences.PerformanceWarning;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async Task FileLogEnabledChanged(bool value)
|
||||
{
|
||||
FileLogEnabled = value;
|
||||
UpdatePreferences.FileLogEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
protected async Task FileLogPathChanged(string value)
|
||||
{
|
||||
FileLogPath = value;
|
||||
UpdatePreferences.FileLogPath = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task FileLogBackupEnabledChanged(bool value)
|
||||
{
|
||||
FileLogBackupEnabled = value;
|
||||
UpdatePreferences.FileLogBackupEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task FileLogMaxSizeChanged(int value)
|
||||
{
|
||||
FileLogMaxSize = value;
|
||||
UpdatePreferences.FileLogMaxSize = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task FileLogDeleteOldChanged(bool value)
|
||||
{
|
||||
FileLogDeleteOld = value;
|
||||
UpdatePreferences.FileLogDeleteOld = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task FileLogAgeChanged(int value)
|
||||
{
|
||||
FileLogAge = value;
|
||||
UpdatePreferences.FileLogAge = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task FileLogAgeTypeChanged(int value)
|
||||
{
|
||||
FileLogAgeType = value;
|
||||
UpdatePreferences.FileLogAgeType = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task PerformanceWarningChanged(bool value)
|
||||
{
|
||||
PerformanceWarning = value;
|
||||
UpdatePreferences.PerformanceWarning = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
}
|
||||
}
|
||||
137
Lantean.QBTMud/Components/Options/BitTorrentOptions.razor
Normal file
137
Lantean.QBTMud/Components/Options/BitTorrentOptions.razor
Normal file
@@ -0,0 +1,137 @@
|
||||
@inherits Options
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4 mt-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.subtitle2">Privacy</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Enable DHT (decentralized network) to find more peers" Value="Dht" ValueChanged="DhtChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Enable Peer Exchange (PeX) to find more peers" Value="Pex" ValueChanged="PexChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Enable Local Peer Discovery to find more peers" Value="Lsd" ValueChanged="LsdChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="int" Label="Encryption mode" Value="Encryption" ValueChanged="EncryptionChanged" Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="0">Allow encryption</MudSelectItem>
|
||||
<MudSelectItem Value="1">Require encryption</MudSelectItem>
|
||||
<MudSelectItem Value="2">Disable encryption</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="6">
|
||||
<FieldSwitch Label="Enable anonymous mode" Value="AnonymousMode" ValueChanged="AnonymousModeChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="6">
|
||||
<MudLink Href="https://github.com/qbittorrent/qBittorrent/wiki/Anonymous-Mode" Underline="Underline.Always" Target="https://github.com/qbittorrent/qBittorrent/wiki/Anonymous-Mode">More information</MudLink>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Max active checking torrents" Value="MaxActiveCheckingTorrents" ValueChanged="MaxActiveCheckingTorrentsChanged" Min="0" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.subtitle2">Torrent Queueing</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Queueing enabled" Value="QueueingEnabled" ValueChanged="QueueingEnabledChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Maximum active downloads" Value="MaxActiveDownloads" ValueChanged="MaxActiveDownloadsChanged" Min="-1" Disabled="@(!QueueingEnabled)" Variant="Variant.Outlined" Validation="MaxActiveDownloadsValidation" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Maximum active uploads" Value="MaxActiveUploads" ValueChanged="MaxActiveUploadsChanged" Min="-1" Disabled="@(!QueueingEnabled)" Variant="Variant.Outlined" Validation="MaxActiveUploadsValidation" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Maximum active torrents" Value="MaxActiveTorrents" ValueChanged="MaxActiveTorrentsChanged" Min="-1" Disabled="@(!QueueingEnabled)" Variant="Variant.Outlined" Validation="MaxActiveTorrentsValidation" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Do not count slow torrents in these limits" Value="DontCountSlowTorrents" ValueChanged="DontCountSlowTorrentsChanged" Disabled="@(!QueueingEnabled)" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Download rate threshold" Value="SlowTorrentDlRateThreshold" ValueChanged="SlowTorrentDlRateThresholdChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB/s" Validation="SlowTorrentDlRateThresholdValidation" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Upload rate threshold" Value="SlowTorrentUlRateThreshold" ValueChanged="SlowTorrentUlRateThresholdChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB/s" Validation="SlowTorrentUlRateThresholdValidation" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Torrent inactivity timer" Value="SlowTorrentInactiveTimer" ValueChanged="SlowTorrentInactiveTimerChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB/s" Validation="SlowTorrentInactiveTimerValidation" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.subtitle2">Seeding Limits</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="3">
|
||||
<FieldSwitch Label="When ratio reaches" Value="MaxRatioEnabled" ValueChanged="MaxRatioEnabledChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="9">
|
||||
<MudNumericField T="int" Label="" Value="MaxRatio" ValueChanged="MaxRatioChanged" Disabled="@(!MaxRatioEnabled)" Min="0" Max="9998" Variant="Variant.Outlined" Validation="MaxRatioValidation" />
|
||||
</MudItem>
|
||||
<MudItem xs="3">
|
||||
<FieldSwitch Label="When total seeding time reaches" Value="MaxSeedingTimeEnabled" ValueChanged="MaxSeedingTimeEnabledChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="9">
|
||||
<MudNumericField T="int" Label="minutes" Value="MaxSeedingTime" ValueChanged="MaxSeedingTimeChanged" Disabled="@(!MaxSeedingTimeEnabled)" Min="0" Max="525600" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="minutes" Validation="MaxSeedingTimeValidation" />
|
||||
</MudItem>
|
||||
<MudItem xs="3">
|
||||
<FieldSwitch Label="When inactive seeding time reaches" Value="MaxInactiveSeedingTimeEnabled" ValueChanged="MaxInactiveSeedingTimeEnabledChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="9">
|
||||
<MudNumericField T="int" Label="minutes" Value="MaxInactiveSeedingTime" ValueChanged="MaxInactiveSeedingTimeChanged" Disabled="@(!MaxInactiveSeedingTimeEnabled)" Min="0" Max="525600" Variant="Variant.Outlined" Validation="MaxInactiveSeedingTimeValidation" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="int" Value="MaxRatioAct" ValueChanged="MaxRatioActChanged" Disabled="@(!MaxRatioEnabled && !MaxSeedingTimeEnabled && !MaxInactiveSeedingTimeEnabled)" Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="0">Stop torrent</MudSelectItem>
|
||||
<MudSelectItem Value="1">Remove torrent</MudSelectItem>
|
||||
<MudSelectItem Value="2">Remove torrent and its files</MudSelectItem>
|
||||
<MudSelectItem Value="3">Enable super seeding for torrent</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.subtitle2">Seeding Limits</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Automatically add these trackers to new downloads" Value="AddTrackersEnabled" ValueChanged="AddTrackersEnabledChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="Trackers" Value="AddTrackers" ValueChanged="AddTrackersChanged" Lines="5" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
334
Lantean.QBTMud/Components/Options/BitTorrentOptions.razor.cs
Normal file
334
Lantean.QBTMud/Components/Options/BitTorrentOptions.razor.cs
Normal file
@@ -0,0 +1,334 @@
|
||||
namespace Lantean.QBTMud.Components.Options
|
||||
{
|
||||
public partial class BitTorrentOptions : Options
|
||||
{
|
||||
protected bool Dht { get; private set; }
|
||||
protected bool Pex { get; private set; }
|
||||
protected bool Lsd { get; private set; }
|
||||
protected int Encryption { get; private set; }
|
||||
protected bool AnonymousMode { get; private set; }
|
||||
protected int MaxActiveCheckingTorrents { get; private set; }
|
||||
protected bool QueueingEnabled { get; private set; }
|
||||
protected int MaxActiveDownloads { get; private set; }
|
||||
protected int MaxActiveUploads { get; private set; }
|
||||
protected int MaxActiveTorrents { get; private set; }
|
||||
protected bool DontCountSlowTorrents { get; private set; }
|
||||
protected int SlowTorrentDlRateThreshold { get; private set; }
|
||||
protected int SlowTorrentUlRateThreshold { get; private set; }
|
||||
protected int SlowTorrentInactiveTimer { get; private set; }
|
||||
protected bool MaxRatioEnabled { get; private set; }
|
||||
protected int MaxRatio { get; private set; }
|
||||
protected bool MaxSeedingTimeEnabled { get; private set; }
|
||||
protected int MaxSeedingTime { get; private set; }
|
||||
protected int MaxRatioAct { get; private set; }
|
||||
protected bool MaxInactiveSeedingTimeEnabled { get; private set; }
|
||||
protected int MaxInactiveSeedingTime { get; private set; }
|
||||
protected bool AddTrackersEnabled { get; private set; }
|
||||
protected string? AddTrackers { get; private set; }
|
||||
|
||||
protected Func<int, string?> MaxActiveDownloadsValidation = value =>
|
||||
{
|
||||
if (value < -1)
|
||||
{
|
||||
return "Maximum active downloads must be greater than -1.";
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
protected Func<int, string?> MaxActiveUploadsValidation = value =>
|
||||
{
|
||||
if (value < -1)
|
||||
{
|
||||
return "Maximum active uploads must be greater than -1.";
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
protected Func<int, string?> MaxActiveTorrentsValidation = value =>
|
||||
{
|
||||
if (value < -1)
|
||||
{
|
||||
return "Maximum active torrents must be greater than -1.";
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
protected Func<int, string?> SlowTorrentDlRateThresholdValidation = value =>
|
||||
{
|
||||
if (value < 1)
|
||||
{
|
||||
return "Download rate threshold must be greater than 0.";
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
protected Func<int, string?> SlowTorrentUlRateThresholdValidation = value =>
|
||||
{
|
||||
if (value < 1)
|
||||
{
|
||||
return "Upload rate threshold must be greater than 0.";
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
protected Func<int, string?> SlowTorrentInactiveTimerValidation = value =>
|
||||
{
|
||||
if (value < 1)
|
||||
{
|
||||
return "Torrent inactivity timer must be greater than 0.";
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
protected Func<int, string?> MaxRatioValidation = value =>
|
||||
{
|
||||
if (value < 0 || value > 9998)
|
||||
{
|
||||
return "Share ratio limit must be between 0 and 9998.";
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
protected Func<int, string?> MaxSeedingTimeValidation = value =>
|
||||
{
|
||||
if (value < 0 || value > 525600)
|
||||
{
|
||||
return "Seeding time limit must be between 0 and 525600 minutes.";
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
protected Func<int, string?> MaxInactiveSeedingTimeValidation = value =>
|
||||
{
|
||||
if (value < 0 || value > 525600)
|
||||
{
|
||||
return "Seeding time limit must be between 0 and 525600 minutes.";
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
protected override bool SetOptions()
|
||||
{
|
||||
if (Preferences is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Dht = Preferences.Dht;
|
||||
Pex = Preferences.Pex;
|
||||
Lsd = Preferences.Lsd;
|
||||
Encryption = Preferences.Encryption;
|
||||
AnonymousMode = Preferences.AnonymousMode;
|
||||
MaxActiveCheckingTorrents = Preferences.MaxActiveCheckingTorrents;
|
||||
QueueingEnabled = Preferences.QueueingEnabled;
|
||||
MaxActiveDownloads = Preferences.MaxActiveDownloads;
|
||||
MaxActiveUploads = Preferences.MaxActiveUploads;
|
||||
MaxActiveTorrents = Preferences.MaxActiveTorrents;
|
||||
DontCountSlowTorrents = Preferences.DontCountSlowTorrents;
|
||||
SlowTorrentDlRateThreshold = Preferences.SlowTorrentDlRateThreshold;
|
||||
SlowTorrentUlRateThreshold = Preferences.SlowTorrentUlRateThreshold;
|
||||
SlowTorrentInactiveTimer = Preferences.SlowTorrentInactiveTimer;
|
||||
MaxRatioEnabled = Preferences.MaxRatioEnabled;
|
||||
MaxRatio = Preferences.MaxRatio;
|
||||
|
||||
if (Preferences.MaxSeedingTimeEnabled)
|
||||
{
|
||||
MaxSeedingTimeEnabled = true;
|
||||
MaxSeedingTime = Preferences.MaxSeedingTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
MaxSeedingTimeEnabled = false;
|
||||
MaxSeedingTime = 1440;
|
||||
}
|
||||
|
||||
MaxRatioAct = Preferences.MaxRatioAct;
|
||||
|
||||
if (Preferences.MaxInactiveSeedingTimeEnabled)
|
||||
{
|
||||
MaxInactiveSeedingTimeEnabled = true;
|
||||
MaxSeedingTime = Preferences.MaxInactiveSeedingTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
MaxInactiveSeedingTimeEnabled = false;
|
||||
MaxInactiveSeedingTime = 1440;
|
||||
}
|
||||
|
||||
AddTrackersEnabled = Preferences.AddTrackersEnabled;
|
||||
AddTrackers = Preferences.AddTrackers;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async Task DhtChanged(bool value)
|
||||
{
|
||||
Dht = value;
|
||||
UpdatePreferences.Dht = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task PexChanged(bool value)
|
||||
{
|
||||
Pex = value;
|
||||
UpdatePreferences.Pex = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task LsdChanged(bool value)
|
||||
{
|
||||
Lsd = value;
|
||||
UpdatePreferences.Lsd = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task EncryptionChanged(int value)
|
||||
{
|
||||
Encryption = value;
|
||||
UpdatePreferences.Encryption = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task AnonymousModeChanged(bool value)
|
||||
{
|
||||
AnonymousMode = value;
|
||||
UpdatePreferences.AnonymousMode = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task MaxActiveCheckingTorrentsChanged(int value)
|
||||
{
|
||||
MaxActiveCheckingTorrents = value;
|
||||
UpdatePreferences.MaxActiveCheckingTorrents = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task QueueingEnabledChanged(bool value)
|
||||
{
|
||||
QueueingEnabled = value;
|
||||
UpdatePreferences.QueueingEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task MaxActiveDownloadsChanged(int value)
|
||||
{
|
||||
MaxActiveDownloads = value;
|
||||
UpdatePreferences.MaxActiveDownloads = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task MaxActiveUploadsChanged(int value)
|
||||
{
|
||||
MaxActiveUploads = value;
|
||||
UpdatePreferences.MaxActiveUploads = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task MaxActiveTorrentsChanged(int value)
|
||||
{
|
||||
MaxActiveTorrents = value;
|
||||
UpdatePreferences.MaxActiveTorrents = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task DontCountSlowTorrentsChanged(bool value)
|
||||
{
|
||||
DontCountSlowTorrents = value;
|
||||
UpdatePreferences.DontCountSlowTorrents = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task SlowTorrentDlRateThresholdChanged(int value)
|
||||
{
|
||||
SlowTorrentDlRateThreshold = value;
|
||||
UpdatePreferences.SlowTorrentDlRateThreshold = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task SlowTorrentUlRateThresholdChanged(int value)
|
||||
{
|
||||
SlowTorrentUlRateThreshold = value;
|
||||
UpdatePreferences.SlowTorrentUlRateThreshold = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task SlowTorrentInactiveTimerChanged(int value)
|
||||
{
|
||||
SlowTorrentInactiveTimer = value;
|
||||
UpdatePreferences.SlowTorrentInactiveTimer = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task MaxRatioEnabledChanged(bool value)
|
||||
{
|
||||
MaxRatioEnabled = value;
|
||||
UpdatePreferences.MaxRatioEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task MaxRatioChanged(int value)
|
||||
{
|
||||
MaxRatio = value;
|
||||
UpdatePreferences.MaxRatio = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task MaxSeedingTimeEnabledChanged(bool value)
|
||||
{
|
||||
MaxSeedingTimeEnabled = value;
|
||||
UpdatePreferences.MaxSeedingTimeEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task MaxSeedingTimeChanged(int value)
|
||||
{
|
||||
MaxSeedingTime = value;
|
||||
UpdatePreferences.MaxSeedingTime = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task MaxRatioActChanged(int value)
|
||||
{
|
||||
MaxRatioAct = value;
|
||||
UpdatePreferences.MaxRatioAct = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task MaxInactiveSeedingTimeEnabledChanged(bool value)
|
||||
{
|
||||
MaxInactiveSeedingTimeEnabled = value;
|
||||
UpdatePreferences.MaxInactiveSeedingTimeEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task MaxInactiveSeedingTimeChanged(int value)
|
||||
{
|
||||
MaxInactiveSeedingTime = value;
|
||||
UpdatePreferences.MaxInactiveSeedingTime = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task AddTrackersEnabledChanged(bool value)
|
||||
{
|
||||
AddTrackersEnabled = value;
|
||||
UpdatePreferences.AddTrackersEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task AddTrackersChanged(string value)
|
||||
{
|
||||
AddTrackers = value;
|
||||
UpdatePreferences.AddTrackers = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
}
|
||||
}
|
||||
163
Lantean.QBTMud/Components/Options/ConnectionOptions.razor
Normal file
163
Lantean.QBTMud/Components/Options/ConnectionOptions.razor
Normal file
@@ -0,0 +1,163 @@
|
||||
@inherits Options
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4 mt-4">
|
||||
<MudCardContent>
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="int" Label="Peer connection protocol" Value="BittorrentProtocol" ValueChanged="BittorrentProtocolChanged" Variant="Variant.Outlined">
|
||||
<MudSelectItem T="int" Value="0">TCP and μTP</MudSelectItem>
|
||||
<MudSelectItem T="int" Value="1">TCP</MudSelectItem>
|
||||
<MudSelectItem T="int" Value="2">μTP</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.subtitle2">Listening Port</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Port used for incoming connections" Value="ListenPort" ValueChanged="ListenPortChanged" Min="@MinNonNegativePortValue" Max="@MaxPortValue" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentIcon="@CustomIcons.Random" OnAdornmentClick="GenerateRandomPort" HelperText="Set to 0 to let your system pick an unused port" Validation="PortNonNegativeValidation" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Use UPnp / NAT-PMP port forwarding from my router" Value="Upnp" ValueChanged="UpnpChanged" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.subtitle2">Connections Limits</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12" md="6">
|
||||
<FieldSwitch Label="Global maximum number of connections" Value="MaxConnecEnabled" ValueChanged="MaxConnecEnabledChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="6">
|
||||
<MudNumericField T="int" Label="Connections" Value="MaxConnec" ValueChanged="MaxConnecChanged" Min="0" Disabled="@(!MaxConnecEnabled)" Variant="Variant.Outlined" Validation="MaxConnectValidation" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="6">
|
||||
<FieldSwitch Label="Maximum number of connections per torrent" Value="MaxConnecPerTorrentEnabled" ValueChanged="MaxConnecPerTorrentEnabledChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="6">
|
||||
<MudNumericField T="int" Label="Connections" Value="MaxConnecPerTorrent" ValueChanged="MaxConnecPerTorrentChanged" Min="0" Disabled="@(!MaxConnecPerTorrentEnabled)" Variant="Variant.Outlined" Validation="MaxConnecPerTorrentValidation" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="6">
|
||||
<FieldSwitch Label="Global maximum number of upload slots" Value="MaxUploadsEnabled" ValueChanged="MaxUploadsEnabledChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="6">
|
||||
<MudNumericField T="int" Label="Slots" Value="MaxUploads" ValueChanged="MaxUploadsChanged" Min="0" Disabled="@(!MaxUploadsEnabled)" Variant="Variant.Outlined" Validation="MaxUploadsValidation" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="6">
|
||||
<FieldSwitch Label="Maximum number of upload slots per torrent" Value="MaxUploadsPerTorrentEnabled" ValueChanged="MaxUploadsPerTorrentEnabledChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="6">
|
||||
<MudNumericField T="int" Label="Slots" Value="MaxUploadsPerTorrent" ValueChanged="MaxUploadsPerTorrentChanged" Min="0" Disabled="@(!MaxUploadsPerTorrentEnabled)" Variant="Variant.Outlined" Validation="MaxUploadsPerTorrentValidation" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="I2P (Experimental)" Value="I2pEnabled" ValueChanged="I2pEnabledChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="6">
|
||||
<MudTextField T="string" Label="Host" Value="I2pAddress" ValueChanged="I2pAddressChanged" Disabled="@(!I2pEnabled)" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="6">
|
||||
<MudNumericField T="int" Label="Slots" Value="I2pPort" ValueChanged="I2pPortChanged" Min="0" Max="65535" Disabled="@(!I2pEnabled)" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Mixed mode" Value="I2pMixedMode" ValueChanged="I2pMixedModeChanged" Disabled="@(!I2pEnabled)" HelperText="If "mixed mode" is enabled, I2P torrents are allowed to also get peers from other sources than the tracker, and connect to regular IPs, not providing any anonymization. This may be useful if the user is not interested in the anonymization of I2P, but still wants to be able to connect to I2P peers." />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.subtitle2">Proxy Server</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12" md="4">
|
||||
<MudSelect T="string" Label="Type" Value="ProxyType" ValueChanged="ProxyTypeChanged" Variant="Variant.Outlined">
|
||||
<MudSelectItem T="string" Value="@("None")">None</MudSelectItem>
|
||||
<MudSelectItem T="string" Value="@("SOCKS4")">SOCKS4</MudSelectItem>
|
||||
<MudSelectItem T="string" Value="@("SOCKS5")">SOCKS5</MudSelectItem>
|
||||
<MudSelectItem T="string" Value="@("HTTP")">HTTP</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="4">
|
||||
<MudTextField T="string" Label="Host" Value="ProxyIp" ValueChanged="ProxyIpChanged" Disabled="ProxyDisabled" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="4">
|
||||
<MudNumericField T="int" Label="Port" Value="ProxyPort" ValueChanged="ProxyPortChanged" Min="1" Max="@ConnectionOptions.MaxPortValue" Disabled="ProxyDisabled" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Perform hostname lookup via proxy" Value="ProxyHostnameLookup" ValueChanged="ProxyHostnameLookupChanged" HelperText="If checked, hostname lookups are done via the proxy." />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Authentication" Value="ProxyAuthEnabled" ValueChanged="ProxyAuthEnabledChanged" Disabled="@(ProxyDisabled || ProxySocks4)" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="6">
|
||||
<MudTextField T="string" Label="Username" Value="ProxyUsername" ValueChanged="ProxyUsernameChanged" Disabled="@(ProxyDisabled || ProxySocks4)" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="6">
|
||||
<MudTextField T="string" Label="Password" Value="ProxyPassword" ValueChanged="ProxyPasswordChanged" Disabled="@(ProxyDisabled || ProxySocks4)" Variant="Variant.Outlined " HelperText="Info: The password is saved unencrypted" />
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Use proxy for BitTorrent purposes" Value="ProxyBittorrent" ValueChanged="ProxyBittorrentChanged" Disabled="ProxyDisabled" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Use proxy for peer connections" Value="ProxyPeerConnections" ValueChanged="ProxyPeerConnectionsChanged" Disabled="@(ProxyDisabled || ProxyAuthEnabled)" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Use proxy for RSS purposes" Value="ProxyRss" ValueChanged="ProxyRssChanged" Disabled="@(ProxyDisabled || ProxySocks4)" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Use proxy for general purposes" Value="ProxyMisc" ValueChanged="ProxyMiscChanged" Disabled="@(ProxyDisabled || ProxySocks4)" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.subtitle2">IP Filtering</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="IP Filter" Value="IpFilterEnabled" ValueChanged="IpFilterEnabledChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="Filter path (.dat, .p2p, .p2b)" Value="IpFilterPath" ValueChanged="IpFilterPathChanged" Disabled="@(!IpFilterEnabled)" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Apply to trackers" Value="IpFilterTrackers" ValueChanged="IpFilterTrackersChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="Manually banned IP addresses" Value="BannedIPs" ValueChanged="BannedIPsChanged" Lines="5" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
369
Lantean.QBTMud/Components/Options/ConnectionOptions.razor.cs
Normal file
369
Lantean.QBTMud/Components/Options/ConnectionOptions.razor.cs
Normal file
@@ -0,0 +1,369 @@
|
||||
namespace Lantean.QBTMud.Components.Options
|
||||
{
|
||||
public partial class ConnectionOptions : Options
|
||||
{
|
||||
protected int BittorrentProtocol { get; private set; }
|
||||
protected int ListenPort { get; private set; }
|
||||
protected bool Upnp { get; private set; }
|
||||
protected bool MaxConnecEnabled { get; private set; }
|
||||
protected int MaxConnec { get; private set; }
|
||||
protected bool MaxConnecPerTorrentEnabled { get; private set; }
|
||||
protected int MaxConnecPerTorrent { get; private set; }
|
||||
protected bool MaxUploadsEnabled { get; private set; }
|
||||
protected int MaxUploads { get; private set; }
|
||||
protected bool MaxUploadsPerTorrentEnabled { get; private set; }
|
||||
protected int MaxUploadsPerTorrent { get; private set; }
|
||||
protected bool I2pEnabled { get; private set; }
|
||||
protected string? I2pAddress { get; private set; }
|
||||
protected int I2pPort { get; private set; }
|
||||
protected bool I2pMixedMode { get; private set; }
|
||||
protected bool ProxyDisabled { get; private set; }
|
||||
protected bool ProxySocks4 { get; private set; }
|
||||
protected string? ProxyType { get; private set; }
|
||||
protected string? ProxyIp { get; private set; }
|
||||
protected int ProxyPort { get; private set; }
|
||||
protected bool ProxyAuthEnabled { get; private set; }
|
||||
protected string? ProxyUsername { get; private set; }
|
||||
protected string? ProxyPassword { get; private set; }
|
||||
protected bool ProxyHostnameLookup { get; private set; }
|
||||
protected bool ProxyBittorrent { get; private set; }
|
||||
protected bool ProxyPeerConnections { get; private set; }
|
||||
protected bool ProxyRss { get; private set; }
|
||||
protected bool ProxyMisc { get; private set; }
|
||||
protected bool IpFilterEnabled { get; private set; }
|
||||
protected string? IpFilterPath { get; private set; }
|
||||
protected bool IpFilterTrackers { get; private set; }
|
||||
protected string? BannedIPs { get; private set; }
|
||||
|
||||
protected Func<int, string?> MaxConnectValidation = value =>
|
||||
{
|
||||
if (value < 0)
|
||||
{
|
||||
return "Maximum number of connections limit must be greater than 0 or disabled.";
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
protected Func<int, string?> MaxConnecPerTorrentValidation = value =>
|
||||
{
|
||||
if (value < 0)
|
||||
{
|
||||
return "Maximum number of connections per torrent limit must be greater than 0 or disabled.";
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
protected Func<int, string?> MaxUploadsValidation = value =>
|
||||
{
|
||||
if (value < 0)
|
||||
{
|
||||
return "Global number of upload slots limit must be greater than 0 or disabled.";
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
protected Func<int, string?> MaxUploadsPerTorrentValidation = value =>
|
||||
{
|
||||
if (value < 0)
|
||||
{
|
||||
return "Maximum number of upload slots per torrent limit must be greater than 0 or disabled.";
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
protected override bool SetOptions()
|
||||
{
|
||||
if (Preferences is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
BittorrentProtocol = Preferences.BittorrentProtocol;
|
||||
ListenPort = Preferences.ListenPort;
|
||||
Upnp = Preferences.Upnp;
|
||||
if (Preferences.MaxConnec > 0)
|
||||
{
|
||||
MaxConnecEnabled = true;
|
||||
MaxConnec = Preferences.MaxConnec;
|
||||
}
|
||||
else
|
||||
{
|
||||
MaxConnecEnabled = false;
|
||||
MaxConnec = 500;
|
||||
}
|
||||
|
||||
if (Preferences.MaxConnecPerTorrent > 0)
|
||||
{
|
||||
MaxConnecPerTorrentEnabled = true;
|
||||
MaxConnecPerTorrent = Preferences.MaxConnecPerTorrent;
|
||||
}
|
||||
else
|
||||
{
|
||||
MaxConnecPerTorrentEnabled = false;
|
||||
MaxConnecPerTorrent = 100;
|
||||
}
|
||||
|
||||
if (Preferences.MaxUploads > 0)
|
||||
{
|
||||
MaxUploadsEnabled = true;
|
||||
MaxUploads = Preferences.MaxUploads;
|
||||
}
|
||||
else
|
||||
{
|
||||
MaxUploadsEnabled = false;
|
||||
MaxUploads = 20;
|
||||
}
|
||||
|
||||
if (Preferences.MaxUploadsPerTorrent > 0)
|
||||
{
|
||||
MaxUploadsPerTorrentEnabled = true;
|
||||
MaxUploadsPerTorrent = Preferences.MaxUploadsPerTorrent;
|
||||
}
|
||||
else
|
||||
{
|
||||
MaxUploadsPerTorrentEnabled = false;
|
||||
MaxUploadsPerTorrent = 4;
|
||||
}
|
||||
|
||||
I2pEnabled = Preferences.I2pEnabled;
|
||||
I2pAddress = Preferences.I2pAddress;
|
||||
I2pPort = Preferences.I2pPort;
|
||||
I2pMixedMode = Preferences.I2pMixedMode;
|
||||
|
||||
ProxyType = Preferences.ProxyType;
|
||||
ProxyIp = Preferences.ProxyIp;
|
||||
ProxyPort = Preferences.ProxyPort;
|
||||
ProxyAuthEnabled = Preferences.ProxyAuthEnabled;
|
||||
ProxyUsername = Preferences.ProxyUsername;
|
||||
ProxyPassword = Preferences.ProxyPassword;
|
||||
ProxyHostnameLookup = Preferences.ProxyHostnameLookup;
|
||||
ProxyBittorrent = Preferences.ProxyBittorrent;
|
||||
ProxyPeerConnections = Preferences.ProxyPeerConnections;
|
||||
ProxyRss = Preferences.ProxyRss;
|
||||
ProxyMisc = Preferences.ProxyMisc;
|
||||
|
||||
IpFilterEnabled = Preferences.IpFilterEnabled;
|
||||
IpFilterPath = Preferences.IpFilterPath;
|
||||
IpFilterTrackers = Preferences.IpFilterTrackers;
|
||||
BannedIPs = Preferences.BannedIPs;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async Task BittorrentProtocolChanged(int value)
|
||||
{
|
||||
BittorrentProtocol = value;
|
||||
UpdatePreferences.BittorrentProtocol = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task ListenPortChanged(int value)
|
||||
{
|
||||
ListenPort = value;
|
||||
UpdatePreferences.ListenPort = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task UpnpChanged(bool value)
|
||||
{
|
||||
Upnp = value;
|
||||
UpdatePreferences.Upnp = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected void MaxConnecEnabledChanged(bool value)
|
||||
{
|
||||
MaxConnecEnabled = value;
|
||||
}
|
||||
|
||||
protected async Task MaxConnecChanged(int value)
|
||||
{
|
||||
MaxConnec = value;
|
||||
UpdatePreferences.MaxConnec = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected void MaxConnecPerTorrentEnabledChanged(bool value)
|
||||
{
|
||||
MaxConnecPerTorrentEnabled = value;
|
||||
}
|
||||
|
||||
protected async Task MaxConnecPerTorrentChanged(int value)
|
||||
{
|
||||
MaxConnecPerTorrent = value;
|
||||
UpdatePreferences.MaxConnecPerTorrent = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected void MaxUploadsEnabledChanged(bool value)
|
||||
{
|
||||
MaxUploadsEnabled = value;
|
||||
}
|
||||
|
||||
protected async Task MaxUploadsChanged(int value)
|
||||
{
|
||||
MaxUploads = value;
|
||||
UpdatePreferences.MaxUploads = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected void MaxUploadsPerTorrentEnabledChanged(bool value)
|
||||
{
|
||||
MaxUploadsPerTorrentEnabled = value;
|
||||
}
|
||||
|
||||
protected async Task MaxUploadsPerTorrentChanged(int value)
|
||||
{
|
||||
MaxUploadsPerTorrent = value;
|
||||
UpdatePreferences.MaxUploadsPerTorrent = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task I2pEnabledChanged(bool value)
|
||||
{
|
||||
I2pEnabled = value;
|
||||
UpdatePreferences.I2pEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task I2pAddressChanged(string value)
|
||||
{
|
||||
I2pAddress = value;
|
||||
UpdatePreferences.I2pAddress = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task I2pPortChanged(int value)
|
||||
{
|
||||
I2pPort = value;
|
||||
UpdatePreferences.I2pPort = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task I2pMixedModeChanged(bool value)
|
||||
{
|
||||
I2pMixedMode = value;
|
||||
UpdatePreferences.I2pMixedMode = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task ProxyTypeChanged(string value)
|
||||
{
|
||||
ProxyType = value;
|
||||
UpdatePreferences.ProxyType = value;
|
||||
ProxyDisabled = value == "None";
|
||||
ProxySocks4 = value == "SOCKS4";
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task ProxyIpChanged(string value)
|
||||
{
|
||||
ProxyIp = value;
|
||||
UpdatePreferences.ProxyIp = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task ProxyPortChanged(int value)
|
||||
{
|
||||
ProxyPort = value;
|
||||
UpdatePreferences.ProxyPort = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task ProxyAuthEnabledChanged(bool value)
|
||||
{
|
||||
ProxyAuthEnabled = value;
|
||||
UpdatePreferences.ProxyAuthEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task ProxyUsernameChanged(string value)
|
||||
{
|
||||
ProxyUsername = value;
|
||||
UpdatePreferences.ProxyUsername = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task ProxyPasswordChanged(string value)
|
||||
{
|
||||
ProxyPassword = value;
|
||||
UpdatePreferences.ProxyPassword = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task ProxyHostnameLookupChanged(bool value)
|
||||
{
|
||||
ProxyHostnameLookup = value;
|
||||
UpdatePreferences.ProxyHostnameLookup = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task ProxyBittorrentChanged(bool value)
|
||||
{
|
||||
ProxyBittorrent = value;
|
||||
UpdatePreferences.ProxyBittorrent = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task ProxyPeerConnectionsChanged(bool value)
|
||||
{
|
||||
ProxyPeerConnections = value;
|
||||
UpdatePreferences.ProxyPeerConnections = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task ProxyRssChanged(bool value)
|
||||
{
|
||||
ProxyRss = value;
|
||||
UpdatePreferences.ProxyRss = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task ProxyMiscChanged(bool value)
|
||||
{
|
||||
ProxyMisc = value;
|
||||
UpdatePreferences.ProxyMisc = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task IpFilterEnabledChanged(bool value)
|
||||
{
|
||||
IpFilterEnabled = value;
|
||||
UpdatePreferences.IpFilterEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task IpFilterPathChanged(string value)
|
||||
{
|
||||
IpFilterPath = value;
|
||||
UpdatePreferences.IpFilterPath = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task IpFilterTrackersChanged(bool value)
|
||||
{
|
||||
IpFilterTrackers = value;
|
||||
UpdatePreferences.IpFilterTrackers = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task BannedIPsChanged(string value)
|
||||
{
|
||||
BannedIPs = value;
|
||||
UpdatePreferences.BannedIPs = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task GenerateRandomPort()
|
||||
{
|
||||
var random = new Random();
|
||||
var port = random.Next(MinPortValue, MaxPortValue);
|
||||
|
||||
await ListenPortChanged(port);
|
||||
}
|
||||
}
|
||||
}
|
||||
309
Lantean.QBTMud/Components/Options/DownloadsOptions.razor
Normal file
309
Lantean.QBTMud/Components/Options/DownloadsOptions.razor
Normal file
@@ -0,0 +1,309 @@
|
||||
@inherits Options
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4 mt-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.subtitle2">When adding a torrent</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="string" Label="Torrent content layout" Value="TorrentContentLayout" ValueChanged="TorrentContentLayoutChanged" Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="@("Original")">Original</MudSelectItem>
|
||||
<MudSelectItem Value="@("Subfolder")">Create subfolder</MudSelectItem>
|
||||
<MudSelectItem Value="@("NoSubfolder")">Don't create subfolder</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Add to top of queue" Value="AddToTopOfQueue" ValueChanged="AddToTopOfQueueChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Do not start the download automatically" Value="StartPausedEnabled" ValueChanged="StartPausedEnabledChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="string" Label="Torrent stop condition" Value="TorrentStopCondition" ValueChanged="TorrentStopConditionChanged" Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="@("None")">None</MudSelectItem>
|
||||
<MudSelectItem Value="@("MetadataReceived")">Metadata received</MudSelectItem>
|
||||
<MudSelectItem Value="@("FilesChecked")">Files Checked</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Delete .torrent files afterwards" Value="AutoDeleteMode" ValueChanged="AutoDeleteModeChanged" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.subtitle2">Files</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Pre-allocate disk space for all files" Value="PreallocateAll" ValueChanged="PreallocateAllChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Append .!qB extension to incomplete files" Value="IncompleteFilesExt" ValueChanged="IncompleteFilesExtChanged" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.subtitle2">Saving Management</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="bool" Label="Default Torrent Management Mode" Value="AutoTmmEnabled" ValueChanged="AutoDeleteModeChanged" Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="false">Manual</MudSelectItem>
|
||||
<MudSelectItem Value="true">Automatic</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="bool" Label="When Torrent Category changed" Value="TorrentChangedTmmEnabled" ValueChanged="TorrentChangedTmmEnabledChanged" Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="true">Relocate torrent</MudSelectItem>
|
||||
<MudSelectItem Value="false">Switch torrent to Manual Mode</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="bool" Label="When Default Save Path changed" Value="SavePathChangedTmmEnabled" ValueChanged="SavePathChangedTmmEnabledChanged" Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="true">Relocate affected torrents</MudSelectItem>
|
||||
<MudSelectItem Value="false">Switch affected torrents to Manual Mode</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="bool" Label="When Category Save Path changed" Value="CategoryChangedTmmEnabled" ValueChanged="CategoryChangedTmmEnabledChanged" Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="true">Relocate affected torrents</MudSelectItem>
|
||||
<MudSelectItem Value="false">Switch affected torrents to Manual Mode</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Use Subcategories" Value="UseSubcategories" ValueChanged="UseSubcategoriesChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="Default Save Path" Value="SavePath" ValueChanged="SavePathChanged" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudGrid>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<FieldSwitch Label="Keep incomplete torrents in" Value="TempPathEnabled" ValueChanged="TempPathEnabledChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="9">
|
||||
<MudTextField T="string" Label="Path" Value="TempPath" ValueChanged="TempPathChanged" Disabled="@(!TempPathEnabled)" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudGrid>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<FieldSwitch Label="Copy .torrent files to" Value="ExportDirEnabled" ValueChanged="ExportDirEnabledChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="9">
|
||||
<MudTextField T="string" Label="Path" Value="ExportDir" ValueChanged="ExportDirChanged" Disabled="@(!TempPathEnabled)" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudGrid>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<FieldSwitch Label="Copy .torrent files for finished downloads to" Value="ExportDirFinEnabled" ValueChanged="ExportDirFinEnabledChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="9">
|
||||
<MudTextField T="string" Label="Path" Value="ExportDirFin" ValueChanged="ExportDirFinChanged" Disabled="@(!TempPathEnabled)" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.subtitle2">Automatically add torrents from</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudSimpleTable>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Monitored Folder</th>
|
||||
<th>Override Save Location</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var item in ScanDirs)
|
||||
{
|
||||
<tr>
|
||||
<td>
|
||||
<MudTextField T="string" Label="Path" Value="@item.Key" ValueChanged="@(v => ScanDirsKeyChanged(item.Key, v))" Validation="IsValidNewKey" />
|
||||
</td>
|
||||
<td>
|
||||
<MudGrid>
|
||||
<MudItem xs="@(item.Value.SavePath is null ? 11 : 3)">
|
||||
<MudSelect T="string" Value="@item.Value.ToString()" ValueChanged="@(v => ScanDirsValueChanged(item.Key, v))" Variant="Variant.Outlined">
|
||||
<MudSelectItem T="string" Value="@("0")">Monitored folder</MudSelectItem>
|
||||
<MudSelectItem T="string" Value="@("1")">Default save location</MudSelectItem>
|
||||
<MudSelectItem T="string" Value="@(item.Value.SavePath is not null ? item.Value.SavePath : "")">Other...</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
@if (item.Value.SavePath is not null)
|
||||
{
|
||||
<MudItem xs="8">
|
||||
<MudTextField T="string" Label="Path" Value="@item.Value.SavePath" ValueChanged="@(v => ScanDirsValueChanged(item.Key, v))" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
}
|
||||
<MudItem xs="1">
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.Remove" OnClick="@(() => RemoveExistingScanDir(item.Key))" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@for (int i = 0; i < AddedScanDirs.Count; i++)
|
||||
{
|
||||
var item = AddedScanDirs[i];
|
||||
var index = i;
|
||||
var isLast = i == AddedScanDirs.Count - 1;
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<MudTextField T="string" Label="Path" Value="@item.Key" ValueChanged="@(v => AddedScanDirsKeyChanged(index, v))" Validation="IsValidNewKey" />
|
||||
</td>
|
||||
<td>
|
||||
<MudGrid>
|
||||
<MudItem xs="@(item.Value.SavePath is null ? (isLast ? 11 : 12) : 3)">
|
||||
<MudSelect T="string" Value="@item.Value.ToString()" ValueChanged="@(v => AddedScanDirsValueChanged(index, v))" Variant="Variant.Outlined">
|
||||
<MudSelectItem T="string" Value="@("0")">Monitored folder</MudSelectItem>
|
||||
<MudSelectItem T="string" Value="@("1")">Default save location</MudSelectItem>
|
||||
<MudSelectItem T="string" Value="@(item.Value.SavePath is not null ? item.Value.SavePath : "")">Other...</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
@if (item.Value.SavePath is not null)
|
||||
{
|
||||
<MudItem xs="@(isLast ? 8 : 9)">
|
||||
<MudTextField T="string" Label="Path" Value="@item.Value.SavePath" ValueChanged="@(v => AddedScanDirsValueChanged(index, v))" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
}
|
||||
<MudItem xs="1">
|
||||
@if (isLast)
|
||||
{
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.Add" OnClick="AddNewScanDir" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.Remove" OnClick="@(() => RemoveAddedScanDir(i))" />
|
||||
}
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</MudSimpleTable>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.subtitle2">Excluded Files</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Excluded file names" Value="ExcludedFileNamesEnabled" ValueChanged="ExcludedFileNamesEnabledChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="Excluded files names" Value="ExcludedFileNames" ValueChanged="ExcludedFileNamesChanged" Lines="5" Disabled="@(!ExcludedFileNamesEnabled)" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.subtitle2">Email Notifications</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Email notification upon download completion" Value="MailNotificationEnabled" ValueChanged="MailNotificationEnabledChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="From" Value="MailNotificationSender" ValueChanged="MailNotificationSenderChanged" Disabled="@(!MailNotificationEnabled)" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="To" Value="MailNotificationEmail" ValueChanged="MailNotificationEmailChanged" Disabled="@(!MailNotificationEnabled)" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="SMTP server" Value="MailNotificationSmtp" ValueChanged="MailNotificationSmtpChanged" Disabled="@(!MailNotificationEnabled)" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="This server requires a secure connection (SSL)" Value="MailNotificationSslEnabled" ValueChanged="MailNotificationSslEnabledChanged" Disabled="@(!MailNotificationEnabled)" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Authentication" Value="MailNotificationAuthEnabled" ValueChanged="MailNotificationAuthEnabledChanged" Disabled="@(!MailNotificationEnabled)" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="Username" Value="MailNotificationUsername" ValueChanged="MailNotificationUsernameChanged" Disabled="@(!(MailNotificationEnabled && MailNotificationAuthEnabled))" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="Password" Value="MailNotificationPassword" ValueChanged="MailNotificationPasswordChanged" Disabled="@(!(MailNotificationEnabled && MailNotificationAuthEnabled))" InputType="InputType.Password" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.subtitle2">Run exernal program</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Run external program on torrent added" Value="AutorunOnTorrentAddedEnabled" ValueChanged="AutorunOnTorrentAddedEnabledChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="External program" Value="AutorunOnTorrentAddedProgram" ValueChanged="AutorunOnTorrentAddedProgramChanged" Disabled="@(!AutorunOnTorrentAddedEnabled)" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Run external program on torrent finished" Value="AutorunEnabled" ValueChanged="AutorunEnabledChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="External program" Value="AutorunProgram" ValueChanged="AutorunProgramChanged" Disabled="@(!AutorunEnabled)" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudText>Supported parameters (case sensitive):</MudText>
|
||||
<MudList T="string" ReadOnly="true">
|
||||
<MudListItem>%N: Torrent name</MudListItem>
|
||||
<MudListItem>%L: Category</MudListItem>
|
||||
<MudListItem>%G: Tags (separated by comma)</MudListItem>
|
||||
<MudListItem>%F: Content path (same as root path for multifile torrent)</MudListItem>
|
||||
<MudListItem>%R: Root path (first torrent subdirectory path)</MudListItem>
|
||||
<MudListItem>%D: Save path</MudListItem>
|
||||
<MudListItem>%C: Number of files</MudListItem>
|
||||
<MudListItem>%Z: Torrent size (bytes)</MudListItem>
|
||||
<MudListItem>%T: Current tracker</MudListItem>
|
||||
<MudListItem>%I: Info hash v1</MudListItem>
|
||||
<MudListItem>%J: Info hash v2</MudListItem>
|
||||
<MudListItem>%K: Torrent ID</MudListItem>
|
||||
</MudList>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
413
Lantean.QBTMud/Components/Options/DownloadsOptions.razor.cs
Normal file
413
Lantean.QBTMud/Components/Options/DownloadsOptions.razor.cs
Normal file
@@ -0,0 +1,413 @@
|
||||
using Lantean.QBitTorrentClient.Models;
|
||||
|
||||
namespace Lantean.QBTMud.Components.Options
|
||||
{
|
||||
public partial class DownloadsOptions : Options
|
||||
{
|
||||
protected string? TorrentContentLayout { get; set; }
|
||||
protected bool AddToTopOfQueue { get; set; }
|
||||
protected bool StartPausedEnabled { get; set; }
|
||||
protected string? TorrentStopCondition { get; set; }
|
||||
protected bool AutoDeleteMode { get; set; }
|
||||
protected bool PreallocateAll { get; set; }
|
||||
protected bool IncompleteFilesExt { get; set; }
|
||||
protected bool AutoTmmEnabled { get; set; }
|
||||
protected bool TorrentChangedTmmEnabled { get; set; }
|
||||
protected bool SavePathChangedTmmEnabled { get; set; }
|
||||
protected bool CategoryChangedTmmEnabled { get; set; }
|
||||
protected bool UseSubcategories { get; set; }
|
||||
protected string? SavePath { get; set; }
|
||||
protected bool TempPathEnabled { get; set; }
|
||||
protected string? TempPath { get; set; }
|
||||
protected bool ExportDirEnabled { get; set; }
|
||||
protected string? ExportDir { get; set; }
|
||||
protected bool ExportDirFinEnabled { get; set; }
|
||||
protected string? ExportDirFin { get; set; }
|
||||
protected Dictionary<string, SaveLocation> ScanDirs { get; set; } = [];
|
||||
protected bool ExcludedFileNamesEnabled { get; set; }
|
||||
protected string? ExcludedFileNames { get; set; }
|
||||
protected bool MailNotificationEnabled { get; set; }
|
||||
protected string? MailNotificationSender { get; set; }
|
||||
protected string? MailNotificationEmail { get; set; }
|
||||
protected string? MailNotificationSmtp { get; set; }
|
||||
protected bool MailNotificationSslEnabled { get; set; }
|
||||
protected bool MailNotificationAuthEnabled { get; set; }
|
||||
protected string? MailNotificationUsername { get; set; }
|
||||
protected string? MailNotificationPassword { get; set; }
|
||||
protected bool AutorunOnTorrentAddedEnabled { get; set; }
|
||||
protected string? AutorunOnTorrentAddedProgram { get; set; }
|
||||
protected bool AutorunEnabled { get; set; }
|
||||
protected string? AutorunProgram { get; set; }
|
||||
|
||||
protected List<KeyValuePair<string, SaveLocation>> AddedScanDirs { get; set; } = [];
|
||||
|
||||
protected override bool SetOptions()
|
||||
{
|
||||
if (Preferences is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// when adding a torrent
|
||||
TorrentContentLayout = Preferences.TorrentContentLayout;
|
||||
AddToTopOfQueue = Preferences.AddToTopOfQueue;
|
||||
StartPausedEnabled = Preferences.StartPausedEnabled;
|
||||
TorrentStopCondition = Preferences.TorrentStopCondition;
|
||||
AutoDeleteMode = Preferences.AutoDeleteMode == 1;
|
||||
PreallocateAll = Preferences.PreallocateAll;
|
||||
IncompleteFilesExt = Preferences.IncompleteFilesExt;
|
||||
|
||||
// saving management
|
||||
AutoTmmEnabled = Preferences.AutoTmmEnabled;
|
||||
TorrentChangedTmmEnabled = Preferences.TorrentChangedTmmEnabled;
|
||||
SavePathChangedTmmEnabled = Preferences.SavePathChangedTmmEnabled;
|
||||
CategoryChangedTmmEnabled = Preferences.CategoryChangedTmmEnabled;
|
||||
UseSubcategories = Preferences.UseSubcategories;
|
||||
SavePath = Preferences.SavePath;
|
||||
TempPathEnabled = Preferences.TempPathEnabled;
|
||||
TempPath = Preferences.TempPath;
|
||||
ExportDir = Preferences.ExportDir;
|
||||
ExportDirEnabled = !string.IsNullOrEmpty(Preferences.ExportDir);
|
||||
ExportDirFin = Preferences.ExportDirFin;
|
||||
ExportDirFinEnabled = !string.IsNullOrEmpty(Preferences.ExportDirFin);
|
||||
|
||||
ScanDirs.Clear();
|
||||
foreach (var dir in Preferences.ScanDirs)
|
||||
{
|
||||
ScanDirs.Add(dir.Key, dir.Value);
|
||||
}
|
||||
|
||||
ExcludedFileNamesEnabled = Preferences.ExcludedFileNamesEnabled;
|
||||
ExcludedFileNames = Preferences.ExcludedFileNames;
|
||||
|
||||
// email notification
|
||||
MailNotificationEnabled = Preferences.MailNotificationEnabled;
|
||||
MailNotificationSender = Preferences.MailNotificationSender;
|
||||
MailNotificationEmail = Preferences.MailNotificationEmail;
|
||||
MailNotificationSmtp = Preferences.MailNotificationSmtp;
|
||||
MailNotificationSslEnabled = Preferences.MailNotificationSslEnabled;
|
||||
MailNotificationAuthEnabled = Preferences.MailNotificationAuthEnabled;
|
||||
MailNotificationUsername = Preferences.MailNotificationUsername;
|
||||
MailNotificationPassword = Preferences.MailNotificationPassword;
|
||||
|
||||
// autorun
|
||||
AutorunOnTorrentAddedEnabled = Preferences.AutorunOnTorrentAddedEnabled;
|
||||
AutorunOnTorrentAddedProgram = Preferences.AutorunOnTorrentAddedProgram;
|
||||
AutorunEnabled = Preferences.AutorunEnabled;
|
||||
AutorunProgram = Preferences.AutorunProgram;
|
||||
|
||||
AddedScanDirs.Clear();
|
||||
AddDefaultScanDir();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async Task TorrentContentLayoutChanged(string value)
|
||||
{
|
||||
TorrentContentLayout = value;
|
||||
UpdatePreferences.TorrentContentLayout = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task AddToTopOfQueueChanged(bool value)
|
||||
{
|
||||
AddToTopOfQueue = value;
|
||||
UpdatePreferences.AddToTopOfQueue = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task StartPausedEnabledChanged(bool value)
|
||||
{
|
||||
StartPausedEnabled = value;
|
||||
UpdatePreferences.StartPausedEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task TorrentStopConditionChanged(string value)
|
||||
{
|
||||
TorrentStopCondition = value;
|
||||
UpdatePreferences.TorrentStopCondition = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task AutoDeleteModeChanged(bool value)
|
||||
{
|
||||
AutoDeleteMode = value;
|
||||
UpdatePreferences.AutoDeleteMode = value ? 1 : 0;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task PreallocateAllChanged(bool value)
|
||||
{
|
||||
PreallocateAll = value;
|
||||
UpdatePreferences.PreallocateAll = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task IncompleteFilesExtChanged(bool value)
|
||||
{
|
||||
IncompleteFilesExt = value;
|
||||
UpdatePreferences.IncompleteFilesExt = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task AutoTmmEnabledChanged(bool value)
|
||||
{
|
||||
AutoTmmEnabled = value;
|
||||
UpdatePreferences.AutoTmmEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task TorrentChangedTmmEnabledChanged(bool value)
|
||||
{
|
||||
TorrentChangedTmmEnabled = value;
|
||||
UpdatePreferences.TorrentChangedTmmEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task SavePathChangedTmmEnabledChanged(bool value)
|
||||
{
|
||||
SavePathChangedTmmEnabled = value;
|
||||
UpdatePreferences.SavePathChangedTmmEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task CategoryChangedTmmEnabledChanged(bool value)
|
||||
{
|
||||
CategoryChangedTmmEnabled = value;
|
||||
UpdatePreferences.CategoryChangedTmmEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task UseSubcategoriesChanged(bool value)
|
||||
{
|
||||
UseSubcategories = value;
|
||||
UpdatePreferences.UseSubcategories = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task SavePathChanged(string value)
|
||||
{
|
||||
SavePath = value;
|
||||
UpdatePreferences.SavePath = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task TempPathEnabledChanged(bool value)
|
||||
{
|
||||
TempPathEnabled = value;
|
||||
UpdatePreferences.TempPathEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task TempPathChanged(string value)
|
||||
{
|
||||
TempPath = value;
|
||||
UpdatePreferences.TempPath = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected void ExportDirEnabledChanged(bool value)
|
||||
{
|
||||
ExportDirEnabled = value;
|
||||
}
|
||||
|
||||
protected async Task ExportDirChanged(string value)
|
||||
{
|
||||
ExportDir = value;
|
||||
UpdatePreferences.ExportDir = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected void ExportDirFinEnabledChanged(bool value)
|
||||
{
|
||||
ExportDirFinEnabled = value;
|
||||
}
|
||||
|
||||
protected async Task ExportDirFinChanged(string value)
|
||||
{
|
||||
ExportDirFin = value;
|
||||
UpdatePreferences.ExportDirFin = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task ScanDirsKeyChanged(string key, string value)
|
||||
{
|
||||
if (ScanDirs.Remove(key, out var location))
|
||||
{
|
||||
ScanDirs[value] = location;
|
||||
}
|
||||
UpdatePreferences.ScanDirs = ScanDirs;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task ScanDirsValueChanged(string key, string value)
|
||||
{
|
||||
ScanDirs[key] = SaveLocation.Create(value);
|
||||
UpdatePreferences.ScanDirs = ScanDirs;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task AddedScanDirsKeyChanged(int index, string key)
|
||||
{
|
||||
if (key == "")
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ScanDirs.Add(key, AddedScanDirs[index].Value);
|
||||
AddedScanDirs.RemoveAt(index);
|
||||
UpdatePreferences.ScanDirs = ScanDirs;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
|
||||
#pragma warning disable S2583 // Conditionally executed code should be reachable
|
||||
if (AddedScanDirs.Count == 0)
|
||||
{
|
||||
AddDefaultScanDir();
|
||||
}
|
||||
#pragma warning restore S2583 // Conditionally executed code should be reachable
|
||||
}
|
||||
|
||||
protected void AddedScanDirsValueChanged(int index, string value)
|
||||
{
|
||||
var existing = AddedScanDirs[index];
|
||||
AddedScanDirs[index] = new KeyValuePair<string, SaveLocation>(existing.Key, SaveLocation.Create(value));
|
||||
}
|
||||
|
||||
protected void AddNewScanDir()
|
||||
{
|
||||
AddDefaultScanDir();
|
||||
}
|
||||
|
||||
protected void RemoveAddedScanDir(int index)
|
||||
{
|
||||
AddedScanDirs.RemoveAt(index);
|
||||
}
|
||||
|
||||
protected async Task RemoveExistingScanDir(string key)
|
||||
{
|
||||
ScanDirs.Remove(key);
|
||||
UpdatePreferences.ScanDirs = ScanDirs;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task ExcludedFileNamesEnabledChanged(bool value)
|
||||
{
|
||||
ExcludedFileNamesEnabled = value;
|
||||
UpdatePreferences.ExcludedFileNamesEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task ExcludedFileNamesChanged(string value)
|
||||
{
|
||||
ExcludedFileNames = value;
|
||||
UpdatePreferences.ExcludedFileNames = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task MailNotificationEnabledChanged(bool value)
|
||||
{
|
||||
MailNotificationEnabled = value;
|
||||
UpdatePreferences.MailNotificationEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task MailNotificationSenderChanged(string value)
|
||||
{
|
||||
MailNotificationSender = value;
|
||||
UpdatePreferences.MailNotificationSender = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task MailNotificationEmailChanged(string value)
|
||||
{
|
||||
MailNotificationEmail = value;
|
||||
UpdatePreferences.MailNotificationEmail = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task MailNotificationSmtpChanged(string value)
|
||||
{
|
||||
MailNotificationSmtp = value;
|
||||
UpdatePreferences.MailNotificationSmtp = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task MailNotificationSslEnabledChanged(bool value)
|
||||
{
|
||||
MailNotificationSslEnabled = value;
|
||||
UpdatePreferences.MailNotificationSslEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task MailNotificationAuthEnabledChanged(bool value)
|
||||
{
|
||||
MailNotificationAuthEnabled = value;
|
||||
UpdatePreferences.MailNotificationAuthEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task MailNotificationUsernameChanged(string value)
|
||||
{
|
||||
MailNotificationUsername = value;
|
||||
UpdatePreferences.MailNotificationUsername = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task MailNotificationPasswordChanged(string value)
|
||||
{
|
||||
MailNotificationPassword = value;
|
||||
UpdatePreferences.MailNotificationPassword = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task AutorunOnTorrentAddedEnabledChanged(bool value)
|
||||
{
|
||||
AutorunOnTorrentAddedEnabled = value;
|
||||
UpdatePreferences.AutorunOnTorrentAddedEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task AutorunOnTorrentAddedProgramChanged(string value)
|
||||
{
|
||||
AutorunOnTorrentAddedProgram = value;
|
||||
UpdatePreferences.AutorunOnTorrentAddedProgram = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task AutorunEnabledChanged(bool value)
|
||||
{
|
||||
AutorunEnabled = value;
|
||||
UpdatePreferences.AutorunEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task AutorunProgramChanged(string value)
|
||||
{
|
||||
AutorunProgram = value;
|
||||
UpdatePreferences.AutorunProgram = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
private void AddDefaultScanDir()
|
||||
{
|
||||
AddedScanDirs.Add(new KeyValuePair<string, SaveLocation>("", SaveLocation.Create(1)));
|
||||
}
|
||||
|
||||
protected Func<string, string?> IsValidNewKey => IsValidNewKeyFunc;
|
||||
|
||||
private string? IsValidNewKeyFunc(string? key)
|
||||
{
|
||||
if (key is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (ScanDirs.ContainsKey(key))
|
||||
{
|
||||
return "A folder with this path already exists";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
67
Lantean.QBTMud/Components/Options/Options.cs
Normal file
67
Lantean.QBTMud/Components/Options/Options.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using Lantean.QBitTorrentClient.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Lantean.QBTMud.Components.Options
|
||||
{
|
||||
public abstract class Options : ComponentBase
|
||||
{
|
||||
private bool _preferencesRead;
|
||||
|
||||
protected const int MinPortValue = 1024;
|
||||
protected const int MinNonNegativePortValue = 0;
|
||||
protected const int MaxPortValue = 65535;
|
||||
|
||||
[Parameter]
|
||||
[EditorRequired]
|
||||
public Preferences? Preferences { get; set; }
|
||||
|
||||
[Parameter]
|
||||
[EditorRequired]
|
||||
public UpdatePreferences UpdatePreferences { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
[EditorRequired]
|
||||
public EventCallback<UpdatePreferences> PreferencesChanged { get; set; }
|
||||
|
||||
protected Func<int, string?> PortNonNegativeValidation = (port) =>
|
||||
{
|
||||
if (port < MinNonNegativePortValue || port > MaxPortValue)
|
||||
{
|
||||
return $"The port used for incoming connections must be between {MinNonNegativePortValue} and {MaxPortValue}.";
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
protected Func<int, string?> PortValidation = (port) =>
|
||||
{
|
||||
if (port < MinPortValue || port > MaxPortValue)
|
||||
{
|
||||
return $"The port used for incoming connections must be between {MinPortValue} and {MaxPortValue}.";
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
public async Task ResetAsync()
|
||||
{
|
||||
SetOptions();
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
UpdatePreferences ??= new UpdatePreferences();
|
||||
|
||||
if (_preferencesRead)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_preferencesRead = SetOptions();
|
||||
}
|
||||
|
||||
protected abstract bool SetOptions();
|
||||
}
|
||||
}
|
||||
58
Lantean.QBTMud/Components/Options/RSSOptions.razor
Normal file
58
Lantean.QBTMud/Components/Options/RSSOptions.razor
Normal file
@@ -0,0 +1,58 @@
|
||||
@inherits Options
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4 mt-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.subtitle2">RSS Reader</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Enable fetching RSS feeds" Value="RssProcessingEnabled" ValueChanged="RssProcessingEnabledChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Feeds refresh interval" Value="RssRefreshInterval" ValueChanged="RssRefreshIntervalChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="min" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Maximum number of articles per feed" Value="RssMaxArticlesPerFeed" ValueChanged="RssMaxArticlesPerFeedChanged" Min="0" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.subtitle2">RSS Torrent Auto Downloader</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Enable auto downloading of RSS torrents" Value="RssAutoDownloadingEnabled" ValueChanged="RssAutoDownloadingEnabledChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudButton OnClick="OpenRssRulesDialog" Variant="Variant.Filled">Edit auto downloading rules</MudButton>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.subtitle2">RSS Smart Episode Filter</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Download REPACK/PROPER episodes" Value="RssDownloadRepackProperEpisodes" ValueChanged="RssDownloadRepackProperEpisodesChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="Filters" Value="RssSmartEpisodeFilters" ValueChanged="RssSmartEpisodeFiltersChanged" Lines="5" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
92
Lantean.QBTMud/Components/Options/RSSOptions.razor.cs
Normal file
92
Lantean.QBTMud/Components/Options/RSSOptions.razor.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
using Lantean.QBTMud.Helpers;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMud.Components.Options
|
||||
{
|
||||
public partial class RSSOptions : Options
|
||||
{
|
||||
[Inject]
|
||||
public IDialogService DialogService { get; set; } = default!;
|
||||
|
||||
protected bool RssProcessingEnabled { get; private set; }
|
||||
protected int RssRefreshInterval { get; private set; }
|
||||
protected long RssFetchDelay { get; private set; }
|
||||
protected int RssMaxArticlesPerFeed { get; private set; }
|
||||
protected bool RssAutoDownloadingEnabled { get; private set; }
|
||||
protected bool RssDownloadRepackProperEpisodes { get; private set; }
|
||||
protected string? RssSmartEpisodeFilters { get; private set; }
|
||||
|
||||
protected override bool SetOptions()
|
||||
{
|
||||
if (Preferences is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
RssProcessingEnabled = Preferences.RssProcessingEnabled;
|
||||
RssRefreshInterval = Preferences.RssRefreshInterval;
|
||||
RssFetchDelay = Preferences.RssFetchDelay;
|
||||
RssMaxArticlesPerFeed = Preferences.RssMaxArticlesPerFeed;
|
||||
RssAutoDownloadingEnabled = Preferences.RssAutoDownloadingEnabled;
|
||||
RssDownloadRepackProperEpisodes = Preferences.RssDownloadRepackProperEpisodes;
|
||||
RssSmartEpisodeFilters = Preferences.RssSmartEpisodeFilters;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async Task RssProcessingEnabledChanged(bool value)
|
||||
{
|
||||
RssProcessingEnabled = value;
|
||||
UpdatePreferences.RssProcessingEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task RssRefreshIntervalChanged(int value)
|
||||
{
|
||||
RssRefreshInterval = value;
|
||||
UpdatePreferences.RssRefreshInterval = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task RssFetchDelayChanged(int value)
|
||||
{
|
||||
RssFetchDelay = value;
|
||||
UpdatePreferences.RssFetchDelay = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task RssMaxArticlesPerFeedChanged(int value)
|
||||
{
|
||||
RssMaxArticlesPerFeed = value;
|
||||
UpdatePreferences.RssMaxArticlesPerFeed = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task RssAutoDownloadingEnabledChanged(bool value)
|
||||
{
|
||||
RssAutoDownloadingEnabled = value;
|
||||
UpdatePreferences.RssAutoDownloadingEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task RssDownloadRepackProperEpisodesChanged(bool value)
|
||||
{
|
||||
RssDownloadRepackProperEpisodes = value;
|
||||
UpdatePreferences.RssDownloadRepackProperEpisodes = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task RssSmartEpisodeFiltersChanged(string value)
|
||||
{
|
||||
RssSmartEpisodeFilters = value;
|
||||
UpdatePreferences.RssSmartEpisodeFilters = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task OpenRssRulesDialog()
|
||||
{
|
||||
await DialogService.InvokeRssRulesDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
81
Lantean.QBTMud/Components/Options/SpeedOptions.razor
Normal file
81
Lantean.QBTMud/Components/Options/SpeedOptions.razor
Normal file
@@ -0,0 +1,81 @@
|
||||
@inherits Options
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4 mt-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.subtitle2">Global Rate Limits</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Upload" Value="UpLimit" ValueChanged="UpLimitChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB/s" HelperText="0 means unlimited" Validation="UpLimitValidation" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Download" Value="DlLimit" ValueChanged="DlLimitChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB/s" HelperText="0 means unlimited" Validation="DlLimitValidation" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.subtitle2">Alternative Rate Limits</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Upload" Value="AltUpLimit" ValueChanged="AltUpLimitChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB/s" HelperText="0 means unlimited" Validation="AltUpLimitValidation" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Download" Value="AltDlLimit" ValueChanged="AltDlLimitChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB/s" HelperText="0 means unlimited" Validation="AltDlLimitValidation" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Schedule the use of alternative rate limits" Value="SchedulerEnabled" ValueChanged="SchedulerEnabledChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="6">
|
||||
<MudTimePicker Label="From" Editable="true" Time="ScheduleFrom" TimeChanged="ScheduleFromChanged" Disabled="@(!SchedulerEnabled)" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="6">
|
||||
<MudTimePicker Label="To" Editable="true" Time="ScheduleTo" TimeChanged="ScheduleToChanged" Disabled="@(!SchedulerEnabled)" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="int" Value="SchedulerDays" ValueChanged="SchedulerDaysChanged" Disabled="@(!SchedulerEnabled)" Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="0">Every day</MudSelectItem>
|
||||
<MudSelectItem Value="1">Weekdays</MudSelectItem>
|
||||
<MudSelectItem Value="2">Weekends</MudSelectItem>
|
||||
<MudSelectItem Value="3">Monday</MudSelectItem>
|
||||
<MudSelectItem Value="4">Tuesday</MudSelectItem>
|
||||
<MudSelectItem Value="5">Wednesday</MudSelectItem>
|
||||
<MudSelectItem Value="6">Thursday</MudSelectItem>
|
||||
<MudSelectItem Value="7">Friday</MudSelectItem>
|
||||
<MudSelectItem Value="8">Saturday</MudSelectItem>
|
||||
<MudSelectItem Value="9">Sunday</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.subtitle2">Rate Limits Settings</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Apply rate limit to µTP protocol" Value="LimitUtpRate" ValueChanged="LimitUtpRateChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Apply rate limit to transport overhead" Value="LimitTcpOverhead" ValueChanged="LimitTcpOverheadChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Apply rate limit to peers on LAN" Value="LimitLanPeers" ValueChanged="LimitLanPeersChanged" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
201
Lantean.QBTMud/Components/Options/SpeedOptions.razor.cs
Normal file
201
Lantean.QBTMud/Components/Options/SpeedOptions.razor.cs
Normal file
@@ -0,0 +1,201 @@
|
||||
namespace Lantean.QBTMud.Components.Options
|
||||
{
|
||||
public partial class SpeedOptions : Options
|
||||
{
|
||||
protected int UpLimit { get; private set; }
|
||||
protected int DlLimit { get; private set; }
|
||||
protected int AltUpLimit { get; private set; }
|
||||
protected int AltDlLimit { get; private set; }
|
||||
protected int BittorrentProtocol { get; private set; }
|
||||
protected bool LimitUtpRate { get; private set; }
|
||||
protected bool LimitTcpOverhead { get; private set; }
|
||||
protected bool LimitLanPeers { get; private set; }
|
||||
protected bool SchedulerEnabled { get; private set; }
|
||||
protected TimeSpan ScheduleFrom { get; private set; }
|
||||
protected TimeSpan ScheduleTo { get; private set; }
|
||||
protected int SchedulerDays { get; private set; }
|
||||
|
||||
protected Func<int, string?> UpLimitValidation = value =>
|
||||
{
|
||||
if (value < 0)
|
||||
{
|
||||
return "Global upload rate limit must be greater than 0 or disabled.";
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
protected Func<int, string?> DlLimitValidation = value =>
|
||||
{
|
||||
if (value < 0)
|
||||
{
|
||||
return "Global download rate limit must be greater than 0 or disabled.";
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
protected Func<int, string?> AltUpLimitValidation = value =>
|
||||
{
|
||||
if (value < 0)
|
||||
{
|
||||
return "Alternative upload rate limit must be greater than 0 or disabled.";
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
protected Func<int, string?> AltDlLimitValidation = value =>
|
||||
{
|
||||
if (value < 0)
|
||||
{
|
||||
return "Alternative download rate limit must be greater than 0 or disabled.";
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
protected override bool SetOptions()
|
||||
{
|
||||
if (Preferences is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
UpLimit = Preferences.UpLimit / 1024;
|
||||
DlLimit = Preferences.DlLimit / 1024;
|
||||
AltUpLimit = Preferences.AltUpLimit / 1024;
|
||||
AltDlLimit = Preferences.AltDlLimit / 1024;
|
||||
BittorrentProtocol = Preferences.BittorrentProtocol;
|
||||
LimitUtpRate = Preferences.LimitUtpRate;
|
||||
LimitTcpOverhead = Preferences.LimitTcpOverhead;
|
||||
LimitLanPeers = Preferences.LimitLanPeers;
|
||||
SchedulerEnabled = Preferences.SchedulerEnabled;
|
||||
ScheduleFrom = TimeSpan.FromMinutes(Preferences.ScheduleFromHour * 60 + Preferences.ScheduleFromMin);
|
||||
ScheduleTo = TimeSpan.FromMinutes(Preferences.ScheduleToHour * 60 + Preferences.ScheduleToMin);
|
||||
SchedulerDays = Preferences.SchedulerDays;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async Task UpLimitChanged(int value)
|
||||
{
|
||||
UpLimit = value;
|
||||
UpdatePreferences.UpLimit = value * 1024;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task DlLimitChanged(int value)
|
||||
{
|
||||
DlLimit = value;
|
||||
UpdatePreferences.DlLimit = value * 1024;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task AltUpLimitChanged(int value)
|
||||
{
|
||||
AltUpLimit = value;
|
||||
UpdatePreferences.AltUpLimit = value * 1024;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task AltDlLimitChanged(int value)
|
||||
{
|
||||
AltDlLimit = value;
|
||||
UpdatePreferences.AltDlLimit = value * 1024;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task BittorrentProtocolChanged(int value)
|
||||
{
|
||||
BittorrentProtocol = value;
|
||||
UpdatePreferences.BittorrentProtocol = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task LimitUtpRateChanged(bool value)
|
||||
{
|
||||
LimitUtpRate = value;
|
||||
UpdatePreferences.LimitUtpRate = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task LimitTcpOverheadChanged(bool value)
|
||||
{
|
||||
LimitTcpOverhead = value;
|
||||
UpdatePreferences.LimitTcpOverhead = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task LimitLanPeersChanged(bool value)
|
||||
{
|
||||
LimitLanPeers = value;
|
||||
UpdatePreferences.LimitLanPeers = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task SchedulerEnabledChanged(bool value)
|
||||
{
|
||||
SchedulerEnabled = value;
|
||||
UpdatePreferences.SchedulerEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task ScheduleFromChanged(TimeSpan? value)
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ScheduleFrom = value.Value;
|
||||
bool hasChanged = false;
|
||||
if (value.Value.Hours != Preferences?.ScheduleFromHour)
|
||||
{
|
||||
UpdatePreferences.ScheduleFromHour = value.Value.Hours;
|
||||
hasChanged = true;
|
||||
}
|
||||
if (value.Value.Minutes != Preferences?.ScheduleFromMin)
|
||||
{
|
||||
UpdatePreferences.ScheduleFromMin = value.Value.Minutes;
|
||||
hasChanged = true;
|
||||
}
|
||||
if (hasChanged)
|
||||
{
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task ScheduleToChanged(TimeSpan? value)
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ScheduleTo = value.Value;
|
||||
bool hasChanged = false;
|
||||
if (value.Value.Hours != Preferences?.ScheduleToHour)
|
||||
{
|
||||
UpdatePreferences.ScheduleToHour = value.Value.Hours;
|
||||
hasChanged = true;
|
||||
}
|
||||
if (value.Value.Minutes != Preferences?.ScheduleToMin)
|
||||
{
|
||||
UpdatePreferences.ScheduleToMin = value.Value.Minutes;
|
||||
hasChanged = true;
|
||||
}
|
||||
if (hasChanged)
|
||||
{
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task SchedulerDaysChanged(int value)
|
||||
{
|
||||
SchedulerDays = value;
|
||||
UpdatePreferences.SchedulerDays = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
}
|
||||
}
|
||||
220
Lantean.QBTMud/Components/Options/WebUIOptions.razor
Normal file
220
Lantean.QBTMud/Components/Options/WebUIOptions.razor
Normal file
@@ -0,0 +1,220 @@
|
||||
@inherits Options
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4 mt-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.subtitle2">Web User Interface (Remote control)</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12" md="8">
|
||||
<MudTextField T="string" Label="Host" Value="WebUiAddress" ValueChanged="WebUiAddressChanged" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="4">
|
||||
<MudNumericField T="int" Label="Port" Value="WebUiPort" ValueChanged="WebUiPortChanged" Min="1" Max="@Options.MaxPortValue" Variant="Variant.Outlined" Validation="WebUiPortValidation" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Use UPnP / NAT-PMP to forward the port from my router" Value="WebUiUpnp" ValueChanged="WebUiUpnpChanged" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.subtitle2">Certificate</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Use HTTPS instead of HTTP" Value="UseHttps" ValueChanged="UseHttpsChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="Certificate" Value="WebUiHttpsCertPath" ValueChanged="WebUiHttpsCertPathChanged" Disabled="@(!UseHttps)" Variant="Variant.Outlined" Validation="WebUiHttpsCertPathValidation" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="Key" Value="WebUiHttpsKeyPath" ValueChanged="WebUiHttpsKeyPathChanged" Disabled="@(!UseHttps)" Variant="Variant.Outlined" Validation="WebUiHttpsKeyPathValidation" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.subtitle2">Authentication</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="Username" Value="WebUiUsername" ValueChanged="WebUiUsernameChanged" Variant="Variant.Outlined" Validation="WebUiUsernameValidation" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="Password" Value="WebUiPassword" ValueChanged="WebUiPasswordChanged" InputType="InputType.Password" Variant="Variant.Outlined" Validation="WebUiPasswordValidation" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Bypass authentication for clients on localhost" Value="BypassLocalAuth" ValueChanged="BypassLocalAuthChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Bypass authentication for clients in whitelisted IP subnets" Value="BypassAuthSubnetWhitelistEnabled" ValueChanged="BypassAuthSubnetWhitelistEnabledChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="Trackers" Value="BypassAuthSubnetWhitelist" ValueChanged="BypassAuthSubnetWhitelistChanged" Lines="5" Disabled="@(!BypassAuthSubnetWhitelistEnabled)" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Ban client after consecutive failures" Value="WebUiMaxAuthFailCount" ValueChanged="WebUiMaxAuthFailCountChanged" Min="0" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="ban for" Value="WebUiBanDuration" ValueChanged="WebUiBanDurationChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="seconds" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" Label="Session timeout" Value="WebUiSessionTimeout" ValueChanged="WebUiSessionTimeoutChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="seconds" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.subtitle2">Alternative Web UI</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Use alternative Web UI" Value="AlternativeWebuiEnabled" ValueChanged="AlternativeWebuiEnabledChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="Files location" Value="AlternativeWebuiPath" ValueChanged="AlternativeWebuiPathChanged" Variant="Variant.Outlined" Validation="AlternativeWebuiPathValidation" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.subtitle2">Security</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Enable clickjacking protection" Value="WebUiClickjackingProtectionEnabled" ValueChanged="WebUiClickjackingProtectionEnabledChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Enable Cross-Site Request Forgery (CSRF) protection" Value="WebUiCsrfProtectionEnabled" ValueChanged="WebUiCsrfProtectionEnabledChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Enable cookie Secure flag (requires HTTPS)" Value="WebUiSecureCookieEnabled" ValueChanged="WebUiSecureCookieEnabledChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Enable Host header validation" Value="WebUiHostHeaderValidationEnabled" ValueChanged="WebUiHostHeaderValidationEnabledChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="Server domains" Value="WebUiDomainList" ValueChanged="WebUiDomainListChanged" Lines="5" Disabled="@(!WebUiHostHeaderValidationEnabled)" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.subtitle2">Security</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Enable clickjacking protection" Value="WebUiClickjackingProtectionEnabled" ValueChanged="WebUiClickjackingProtectionEnabledChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Enable Cross-Site Request Forgery (CSRF) protection" Value="WebUiCsrfProtectionEnabled" ValueChanged="WebUiCsrfProtectionEnabledChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Enable cookie Secure flag (requires HTTPS)" Value="WebUiSecureCookieEnabled" ValueChanged="WebUiSecureCookieEnabledChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Enable Host header validation" Value="WebUiHostHeaderValidationEnabled" ValueChanged="WebUiHostHeaderValidationEnabledChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="Server domains" Value="WebUiDomainList" ValueChanged="WebUiDomainListChanged" Lines="5" Disabled="@(!WebUiHostHeaderValidationEnabled)" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.subtitle2">Http Headers</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Add custom HTTP headers" Value="WebUiUseCustomHttpHeadersEnabled" ValueChanged="WebUiUseCustomHttpHeadersEnabledChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="Server domains" Value="WebUiCustomHttpHeaders" ValueChanged="WebUiCustomHttpHeadersChanged" Lines="5" Disabled="@(!WebUiUseCustomHttpHeadersEnabled)" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.subtitle2">Reverse Proxy</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Enable reverse proxy support" Value="WebUiReverseProxyEnabled" ValueChanged="WebUiReverseProxyEnabledChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="Trusted proxies list" Value="WebUiReverseProxiesList" ValueChanged="WebUiReverseProxiesListChanged" Lines="5" Disabled="@(!WebUiReverseProxyEnabled)" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.subtitle2">Dynamic Domain Name</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Update my dynamic domain name" Value="DyndnsEnabled" ValueChanged="DyndnsEnabledChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="8">
|
||||
<MudSelect T="int" Value="DyndnsService" ValueChanged="DyndnsServiceChanged" Disabled="@(!DyndnsEnabled)" Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="0">DynDNS</MudSelectItem>
|
||||
<MudSelectItem Value="1">NO-IP</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudButton OnClick="RegisterDyndnsService" Disabled="@(!DyndnsEnabled)" Variant="Variant.Filled">Register</MudButton>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="Domain name" Value="DyndnsDomain" ValueChanged="DyndnsDomainChanged" Disabled="@(!DyndnsEnabled)" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="Username" Value="DyndnsUsername" ValueChanged="DyndnsUsernameChanged" Disabled="@(!DyndnsEnabled)" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="Password" Value="DyndnsPassword" ValueChanged="DyndnsPasswordChanged" Disabled="@(!DyndnsEnabled)" InputType="InputType.Password" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
411
Lantean.QBTMud/Components/Options/WebUIOptions.razor.cs
Normal file
411
Lantean.QBTMud/Components/Options/WebUIOptions.razor.cs
Normal file
@@ -0,0 +1,411 @@
|
||||
using Lantean.QBTMud.Interop;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
namespace Lantean.QBTMud.Components.Options
|
||||
{
|
||||
public partial class WebUIOptions : Options
|
||||
{
|
||||
[Inject]
|
||||
public IJSRuntime JSRuntime { get; set; } = default!;
|
||||
|
||||
protected string? Locale { get; private set; }
|
||||
protected bool PerformanceWarning { get; private set; }
|
||||
protected string? WebUiDomainList { get; private set; }
|
||||
protected string? WebUiAddress { get; private set; }
|
||||
protected int WebUiPort { get; private set; }
|
||||
protected bool WebUiUpnp { get; private set; }
|
||||
protected bool UseHttps { get; private set; }
|
||||
protected string? WebUiHttpsCertPath { get; private set; }
|
||||
protected string? WebUiHttpsKeyPath { get; private set; }
|
||||
protected string? WebUiUsername { get; private set; }
|
||||
protected string? WebUiPassword { get; private set; }
|
||||
protected bool BypassLocalAuth { get; private set; }
|
||||
protected bool BypassAuthSubnetWhitelistEnabled { get; private set; }
|
||||
protected string? BypassAuthSubnetWhitelist { get; private set; }
|
||||
protected int WebUiMaxAuthFailCount { get; private set; }
|
||||
protected int WebUiBanDuration { get; private set; }
|
||||
protected int WebUiSessionTimeout { get; private set; }
|
||||
protected bool AlternativeWebuiEnabled { get; private set; }
|
||||
protected string? AlternativeWebuiPath { get; private set; }
|
||||
protected bool WebUiClickjackingProtectionEnabled { get; private set; }
|
||||
protected bool WebUiCsrfProtectionEnabled { get; private set; }
|
||||
protected bool WebUiSecureCookieEnabled { get; private set; }
|
||||
protected bool WebUiHostHeaderValidationEnabled { get; private set; }
|
||||
protected bool WebUiUseCustomHttpHeadersEnabled { get; private set; }
|
||||
protected string? WebUiCustomHttpHeaders { get; private set; }
|
||||
protected bool WebUiReverseProxyEnabled { get; private set; }
|
||||
protected string? WebUiReverseProxiesList { get; private set; }
|
||||
protected bool DyndnsEnabled { get; private set; }
|
||||
protected int DyndnsService { get; private set; }
|
||||
protected string? DyndnsDomain { get; private set; }
|
||||
protected string? DyndnsUsername { get; private set; }
|
||||
protected string? DyndnsPassword { get; private set; }
|
||||
|
||||
protected Func<int, string?> WebUiPortValidation = value =>
|
||||
{
|
||||
if (value < 1 || value > MaxPortValue)
|
||||
{
|
||||
return "The port used for the Web UI must be between 1 and 65535.";
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
protected Func<string?, string?> WebUiHttpsCertPathValidation => WebUiHttpsCertPathValidationFunc;
|
||||
|
||||
protected Func<string?, string?> WebUiHttpsKeyPathValidation => WebUiHttpsKeyPathValidationFunc;
|
||||
|
||||
protected Func<string?, string?> WebUiUsernameValidation = value =>
|
||||
{
|
||||
if (value is null || value.Length < 3)
|
||||
{
|
||||
return "The Web UI username must be at least 3 characters long.";
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
protected Func<string?, string?> WebUiPasswordValidation = value =>
|
||||
{
|
||||
if (value is null || value.Length < 6)
|
||||
{
|
||||
return "The Web UI password must be at least 6 characters long.";
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
protected Func<string?, string?> AlternativeWebuiPathValidation => AlternativeWebuiPathValidationFunc;
|
||||
|
||||
protected string? WebUiHttpsCertPathValidationFunc(string? value)
|
||||
{
|
||||
if (!UseHttps)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (value is not null && value.Length > 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return "HTTPS certificate should not be empty.";
|
||||
}
|
||||
|
||||
protected string? WebUiHttpsKeyPathValidationFunc(string? value)
|
||||
{
|
||||
if (!UseHttps)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (value is not null && value.Length > 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return "HTTPS key should not be empty.";
|
||||
}
|
||||
|
||||
protected string? AlternativeWebuiPathValidationFunc(string? value)
|
||||
{
|
||||
if (!AlternativeWebuiEnabled)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (value is not null && value.Length > 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return "The alternative Web UI files location cannot be blank.";
|
||||
}
|
||||
|
||||
protected override bool SetOptions()
|
||||
{
|
||||
if (Preferences is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Locale = Preferences.Locale;
|
||||
PerformanceWarning = Preferences.PerformanceWarning;
|
||||
WebUiDomainList = Preferences.WebUiDomainList;
|
||||
WebUiAddress = Preferences.WebUiAddress;
|
||||
WebUiPort = Preferences.WebUiPort;
|
||||
WebUiUpnp = Preferences.WebUiUpnp;
|
||||
UseHttps = Preferences.UseHttps;
|
||||
WebUiHttpsCertPath = Preferences.WebUiHttpsCertPath;
|
||||
WebUiHttpsKeyPath = Preferences.WebUiHttpsKeyPath;
|
||||
WebUiUsername = Preferences.WebUiUsername;
|
||||
WebUiPassword = Preferences.WebUiPassword;
|
||||
BypassLocalAuth = Preferences.BypassLocalAuth;
|
||||
BypassAuthSubnetWhitelistEnabled = Preferences.BypassAuthSubnetWhitelistEnabled;
|
||||
BypassAuthSubnetWhitelist = Preferences.BypassAuthSubnetWhitelist;
|
||||
WebUiMaxAuthFailCount = Preferences.WebUiMaxAuthFailCount;
|
||||
WebUiBanDuration = Preferences.WebUiBanDuration;
|
||||
WebUiSessionTimeout = Preferences.WebUiSessionTimeout;
|
||||
AlternativeWebuiEnabled = Preferences.AlternativeWebuiEnabled;
|
||||
AlternativeWebuiPath = Preferences.AlternativeWebuiPath;
|
||||
WebUiClickjackingProtectionEnabled = Preferences.WebUiClickjackingProtectionEnabled;
|
||||
WebUiCsrfProtectionEnabled = Preferences.WebUiCsrfProtectionEnabled;
|
||||
WebUiSecureCookieEnabled = Preferences.WebUiSecureCookieEnabled;
|
||||
WebUiHostHeaderValidationEnabled = Preferences.WebUiHostHeaderValidationEnabled;
|
||||
WebUiUseCustomHttpHeadersEnabled = Preferences.WebUiUseCustomHttpHeadersEnabled;
|
||||
WebUiCustomHttpHeaders = Preferences.WebUiCustomHttpHeaders;
|
||||
WebUiReverseProxyEnabled = Preferences.WebUiReverseProxyEnabled;
|
||||
WebUiReverseProxiesList = Preferences.WebUiReverseProxiesList;
|
||||
DyndnsEnabled = Preferences.DyndnsEnabled;
|
||||
DyndnsService = Preferences.DyndnsService;
|
||||
DyndnsDomain = Preferences.DyndnsDomain;
|
||||
DyndnsUsername = Preferences.DyndnsUsername;
|
||||
DyndnsPassword = Preferences.DyndnsPassword;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async Task LocaleChanged(string value)
|
||||
{
|
||||
Locale = value;
|
||||
UpdatePreferences.Locale = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task PerformanceWarningChanged(bool value)
|
||||
{
|
||||
PerformanceWarning = value;
|
||||
UpdatePreferences.PerformanceWarning = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task WebUiDomainListChanged(string value)
|
||||
{
|
||||
WebUiDomainList = value;
|
||||
UpdatePreferences.WebUiDomainList = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task WebUiAddressChanged(string value)
|
||||
{
|
||||
WebUiAddress = value;
|
||||
UpdatePreferences.WebUiAddress = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task WebUiPortChanged(int value)
|
||||
{
|
||||
WebUiPort = value;
|
||||
UpdatePreferences.WebUiPort = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task WebUiUpnpChanged(bool value)
|
||||
{
|
||||
WebUiUpnp = value;
|
||||
UpdatePreferences.WebUiUpnp = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task UseHttpsChanged(bool value)
|
||||
{
|
||||
UseHttps = value;
|
||||
UpdatePreferences.UseHttps = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task WebUiHttpsCertPathChanged(string value)
|
||||
{
|
||||
WebUiHttpsCertPath = value;
|
||||
UpdatePreferences.WebUiHttpsCertPath = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task WebUiHttpsKeyPathChanged(string value)
|
||||
{
|
||||
WebUiHttpsKeyPath = value;
|
||||
UpdatePreferences.WebUiHttpsKeyPath = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task WebUiUsernameChanged(string value)
|
||||
{
|
||||
WebUiUsername = value;
|
||||
UpdatePreferences.WebUiUsername = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task WebUiPasswordChanged(string value)
|
||||
{
|
||||
WebUiPassword = value;
|
||||
UpdatePreferences.WebUiPassword = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task BypassLocalAuthChanged(bool value)
|
||||
{
|
||||
BypassLocalAuth = value;
|
||||
UpdatePreferences.BypassLocalAuth = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task BypassAuthSubnetWhitelistEnabledChanged(bool value)
|
||||
{
|
||||
BypassAuthSubnetWhitelistEnabled = value;
|
||||
UpdatePreferences.BypassAuthSubnetWhitelistEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task BypassAuthSubnetWhitelistChanged(string value)
|
||||
{
|
||||
BypassAuthSubnetWhitelist = value;
|
||||
UpdatePreferences.BypassAuthSubnetWhitelist = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task WebUiMaxAuthFailCountChanged(int value)
|
||||
{
|
||||
WebUiMaxAuthFailCount = value;
|
||||
UpdatePreferences.WebUiMaxAuthFailCount = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task WebUiBanDurationChanged(int value)
|
||||
{
|
||||
WebUiBanDuration = value;
|
||||
UpdatePreferences.WebUiBanDuration = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task WebUiSessionTimeoutChanged(int value)
|
||||
{
|
||||
WebUiSessionTimeout = value;
|
||||
UpdatePreferences.WebUiSessionTimeout = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task AlternativeWebuiEnabledChanged(bool value)
|
||||
{
|
||||
AlternativeWebuiEnabled = value;
|
||||
UpdatePreferences.AlternativeWebuiEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task AlternativeWebuiPathChanged(string value)
|
||||
{
|
||||
AlternativeWebuiPath = value;
|
||||
UpdatePreferences.AlternativeWebuiPath = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task WebUiClickjackingProtectionEnabledChanged(bool value)
|
||||
{
|
||||
WebUiClickjackingProtectionEnabled = value;
|
||||
UpdatePreferences.WebUiClickjackingProtectionEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task WebUiCsrfProtectionEnabledChanged(bool value)
|
||||
{
|
||||
WebUiCsrfProtectionEnabled = value;
|
||||
UpdatePreferences.WebUiCsrfProtectionEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task WebUiSecureCookieEnabledChanged(bool value)
|
||||
{
|
||||
WebUiSecureCookieEnabled = value;
|
||||
UpdatePreferences.WebUiSecureCookieEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task WebUiHostHeaderValidationEnabledChanged(bool value)
|
||||
{
|
||||
WebUiHostHeaderValidationEnabled = value;
|
||||
UpdatePreferences.WebUiHostHeaderValidationEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task WebUiUseCustomHttpHeadersEnabledChanged(bool value)
|
||||
{
|
||||
WebUiUseCustomHttpHeadersEnabled = value;
|
||||
UpdatePreferences.WebUiUseCustomHttpHeadersEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task WebUiCustomHttpHeadersChanged(string value)
|
||||
{
|
||||
WebUiCustomHttpHeaders = value;
|
||||
UpdatePreferences.WebUiCustomHttpHeaders = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task WebUiReverseProxyEnabledChanged(bool value)
|
||||
{
|
||||
WebUiReverseProxyEnabled = value;
|
||||
UpdatePreferences.WebUiReverseProxyEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task WebUiReverseProxiesListChanged(string value)
|
||||
{
|
||||
WebUiReverseProxiesList = value;
|
||||
UpdatePreferences.WebUiReverseProxiesList = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task DyndnsEnabledChanged(bool value)
|
||||
{
|
||||
DyndnsEnabled = value;
|
||||
UpdatePreferences.DyndnsEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task DyndnsServiceChanged(int value)
|
||||
{
|
||||
DyndnsService = value;
|
||||
UpdatePreferences.DyndnsService = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task DyndnsDomainChanged(string value)
|
||||
{
|
||||
DyndnsDomain = value;
|
||||
UpdatePreferences.DyndnsDomain = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task DyndnsUsernameChanged(string value)
|
||||
{
|
||||
DyndnsUsername = value;
|
||||
UpdatePreferences.DyndnsUsername = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task DyndnsPasswordChanged(string value)
|
||||
{
|
||||
DyndnsPassword = value;
|
||||
UpdatePreferences.DyndnsPassword = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task RegisterDyndnsService()
|
||||
{
|
||||
if (!DyndnsEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#pragma warning disable S1075 // URIs should not be hardcoded
|
||||
var url = DyndnsService switch
|
||||
{
|
||||
0 => "https://www.dyndns.com/account/services/hosts/add.html",
|
||||
1 => "http://www.no-ip.com/services/managed_dns/free_dynamic_dns.html",
|
||||
_ => throw new InvalidOperationException($"DyndnsService value of {DyndnsService} is not supported."),
|
||||
};
|
||||
#pragma warning restore S1075 // URIs should not be hardcoded
|
||||
await JSRuntime.Open(url, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
24
Lantean.QBTMud/Components/PeersTab.razor
Normal file
24
Lantean.QBTMud/Components/PeersTab.razor
Normal file
@@ -0,0 +1,24 @@
|
||||
<ContextMenu @ref="ContextMenu" Dense="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>
|
||||
|
||||
<MudToolBar Gutters="false" Dense="true">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.AddCircle" Color="Color.Info" OnClick="AddPeer">Add peer</MudIconButton>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.DisabledByDefault" Color="Color.Error" OnClick="BanPeerToolbar" Disabled="@(SelectedItem is null)">Ban peer</MudIconButton>
|
||||
<MudDivider Vertical="true" />
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.ViewColumn" Color="Color.Inherit" OnClick="ColumnOptions" title="Choose Columns" />
|
||||
</MudToolBar>
|
||||
|
||||
<DynamicTable T="Peer"
|
||||
ColumnDefinitions="Columns"
|
||||
Items="Peers"
|
||||
MultiSelection="false"
|
||||
SelectOnRowClick="true"
|
||||
OnTableDataLongPress="TableDataLongPress"
|
||||
OnTableDataContextMenu="TableDataContextMenu"
|
||||
SelectedItemChanged="SelectedItemChanged"
|
||||
Class="details-list" />
|
||||
258
Lantean.QBTMud/Components/PeersTab.razor.cs
Normal file
258
Lantean.QBTMud/Components/PeersTab.razor.cs
Normal file
@@ -0,0 +1,258 @@
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBTMud.Components.UI;
|
||||
using Lantean.QBTMud.Helpers;
|
||||
using Lantean.QBTMud.Models;
|
||||
using Lantean.QBTMud.Services;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
using System.Data;
|
||||
using System.Net;
|
||||
|
||||
namespace Lantean.QBTMud.Components
|
||||
{
|
||||
public partial class PeersTab : IAsyncDisposable
|
||||
{
|
||||
private bool _disposedValue;
|
||||
protected string? _oldHash;
|
||||
private int _requestId = 0;
|
||||
private readonly CancellationTokenSource _timerCancellationToken = new();
|
||||
private bool? _showFlags;
|
||||
|
||||
private const string _toolbar = nameof(_toolbar);
|
||||
private const string _context = nameof(_context);
|
||||
|
||||
[Parameter, EditorRequired]
|
||||
public string? Hash { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool Active { get; set; }
|
||||
|
||||
[CascadingParameter(Name = "RefreshInterval")]
|
||||
public int RefreshInterval { get; set; }
|
||||
|
||||
[CascadingParameter]
|
||||
public QBitTorrentClient.Models.Preferences? Preferences { get; set; }
|
||||
|
||||
[Inject]
|
||||
protected IDialogService DialogService { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected IApiClient ApiClient { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected IDataManager DataManager { get; set; } = default!;
|
||||
|
||||
protected PeerList? PeerList { get; set; }
|
||||
|
||||
protected IEnumerable<Peer> Peers => PeerList?.Peers.Select(p => p.Value) ?? [];
|
||||
|
||||
protected bool ShowFlags => _showFlags.GetValueOrDefault();
|
||||
|
||||
protected Peer? ContextMenuItem { get; set; }
|
||||
|
||||
protected Peer? SelectedItem { get; set; }
|
||||
|
||||
protected ContextMenu? ContextMenu { get; set; }
|
||||
|
||||
protected DynamicTable<Peer>? Table { get; set; }
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (Hash is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Active)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Hash != _oldHash)
|
||||
{
|
||||
_oldHash = Hash;
|
||||
_requestId = 0;
|
||||
PeerList = null;
|
||||
}
|
||||
|
||||
var peers = await ApiClient.GetTorrentPeersData(Hash, _requestId);
|
||||
if (PeerList is null || peers.FullUpdate)
|
||||
{
|
||||
PeerList = DataManager.CreatePeerList(peers);
|
||||
}
|
||||
else
|
||||
{
|
||||
DataManager.MergeTorrentPeers(peers, PeerList);
|
||||
}
|
||||
_requestId = peers.RequestId;
|
||||
|
||||
if (Preferences is not null && _showFlags is null)
|
||||
{
|
||||
_showFlags = Preferences.ResolvePeerCountries;
|
||||
}
|
||||
|
||||
if (peers.ShowFlags.HasValue)
|
||||
{
|
||||
_showFlags = peers.ShowFlags.Value;
|
||||
}
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
protected async Task AddPeer()
|
||||
{
|
||||
if (Hash is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var peers = await DialogService.ShowAddPeersDialog();
|
||||
if (peers is null || peers.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await ApiClient.AddPeers([Hash], peers);
|
||||
}
|
||||
|
||||
protected Task BanPeerToolbar()
|
||||
{
|
||||
return BanPeer(SelectedItem);
|
||||
}
|
||||
|
||||
protected Task BanPeerContextMenu()
|
||||
{
|
||||
return BanPeer(ContextMenuItem);
|
||||
}
|
||||
|
||||
private async Task BanPeer(Peer? peer)
|
||||
{
|
||||
if (Hash is null || peer is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
await ApiClient.BanPeers([new QBitTorrentClient.Models.PeerId(peer.IPAddress, peer.Port)]);
|
||||
}
|
||||
|
||||
protected Task TableDataContextMenu(TableDataContextMenuEventArgs<Peer> eventArgs)
|
||||
{
|
||||
return ShowContextMenu(eventArgs.Item, eventArgs.MouseEventArgs);
|
||||
}
|
||||
|
||||
protected Task TableDataLongPress(TableDataLongPressEventArgs<Peer> eventArgs)
|
||||
{
|
||||
return ShowContextMenu(eventArgs.Item, eventArgs.LongPressEventArgs);
|
||||
}
|
||||
|
||||
protected async Task ShowContextMenu(Peer? peer, EventArgs eventArgs)
|
||||
{
|
||||
ContextMenuItem = peer;
|
||||
|
||||
if (ContextMenu is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await ContextMenu.ToggleMenuAsync(eventArgs);
|
||||
}
|
||||
|
||||
protected void SelectedItemChanged(Peer peer)
|
||||
{
|
||||
SelectedItem = peer;
|
||||
}
|
||||
|
||||
protected async Task ColumnOptions()
|
||||
{
|
||||
if (Table is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await Table.ShowColumnOptionsDialog();
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
using (var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(RefreshInterval)))
|
||||
{
|
||||
while (!_timerCancellationToken.IsCancellationRequested && await timer.WaitForNextTickAsync())
|
||||
{
|
||||
if (Hash is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Active)
|
||||
{
|
||||
QBitTorrentClient.Models.TorrentPeers peers;
|
||||
try
|
||||
{
|
||||
peers = await ApiClient.GetTorrentPeersData(Hash, _requestId);
|
||||
}
|
||||
catch (HttpRequestException exception) when (exception.StatusCode == HttpStatusCode.Forbidden || exception.StatusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
_timerCancellationToken.CancelIfNotDisposed();
|
||||
return;
|
||||
}
|
||||
if (PeerList is null || peers.FullUpdate)
|
||||
{
|
||||
PeerList = DataManager.CreatePeerList(peers);
|
||||
}
|
||||
else
|
||||
{
|
||||
DataManager.MergeTorrentPeers(peers, PeerList);
|
||||
}
|
||||
|
||||
_requestId = peers.RequestId;
|
||||
}
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected IEnumerable<ColumnDefinition<Peer>> Columns => ColumnsDefinitions.Where(c => c.Id != "country/region" || _showFlags == true);
|
||||
|
||||
public static List<ColumnDefinition<Peer>> ColumnsDefinitions { get; } =
|
||||
[
|
||||
new ColumnDefinition<Peer>("Country/Region", p => p.Country),
|
||||
new ColumnDefinition<Peer>("IP", p => p.IPAddress),
|
||||
new ColumnDefinition<Peer>("Port", p => p.Port),
|
||||
new ColumnDefinition<Peer>("Connection", p => p.Connection),
|
||||
new ColumnDefinition<Peer>("Flags", p => p.Flags),
|
||||
new ColumnDefinition<Peer>("Client", p => p.Client),
|
||||
new ColumnDefinition<Peer>("Progress", p => p.Progress, p => DisplayHelpers.Percentage(p.Progress)),
|
||||
new ColumnDefinition<Peer>("Download Speed", p => p.DownloadSpeed, p => DisplayHelpers.Speed(p.DownloadSpeed)),
|
||||
new ColumnDefinition<Peer>("Upload Speed", p => p.UploadSpeed, p => DisplayHelpers.Speed(p.UploadSpeed)),
|
||||
new ColumnDefinition<Peer>("Downloaded", p => p.Downloaded, p => @DisplayHelpers.Size(p.Downloaded)),
|
||||
new ColumnDefinition<Peer>("Uploaded", p => p.Uploaded, p => @DisplayHelpers.Size(p.Uploaded)),
|
||||
new ColumnDefinition<Peer>("Relevance", p => p.Relevance, p => @DisplayHelpers.Percentage(p.Relevance)),
|
||||
new ColumnDefinition<Peer>("Files", p => p.Files),
|
||||
];
|
||||
|
||||
protected virtual async Task DisposeAsync(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_timerCancellationToken.Cancel();
|
||||
_timerCancellationToken.Dispose();
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
await DisposeAsync(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
1
Lantean.QBTMud/Components/PieceProgress.razor
Normal file
1
Lantean.QBTMud/Components/PieceProgress.razor
Normal file
@@ -0,0 +1 @@
|
||||
<div id="progress"></div>
|
||||
103
Lantean.QBTMud/Components/PieceProgress.razor.cs
Normal file
103
Lantean.QBTMud/Components/PieceProgress.razor.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
using Lantean.QBitTorrentClient.Models;
|
||||
using Lantean.QBTMud.Interop;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.JSInterop;
|
||||
using MudBlazor;
|
||||
using MudBlazor.Services;
|
||||
|
||||
namespace Lantean.QBTMud.Components
|
||||
{
|
||||
public partial class PieceProgress : IBrowserViewportObserver, IAsyncDisposable
|
||||
{
|
||||
private bool _disposedValue;
|
||||
|
||||
[Inject]
|
||||
public IJSRuntime JSRuntime { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
private IBrowserViewportService BrowserViewportService { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
[EditorRequired]
|
||||
public string Hash { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
[EditorRequired]
|
||||
public IReadOnlyList<PieceState> Pieces { get; set; } = [];
|
||||
|
||||
[CascadingParameter(Name = "IsDarkMode")]
|
||||
public bool IsDarkMode { get; set; }
|
||||
|
||||
[CascadingParameter]
|
||||
public MudTheme Theme { get; set; } = default!;
|
||||
|
||||
public Guid Id => Guid.NewGuid();
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
await RenderPiecesBar();
|
||||
}
|
||||
|
||||
private async Task RenderPiecesBar()
|
||||
{
|
||||
string downloadingColor;
|
||||
string haveColor;
|
||||
string borderColor;
|
||||
if (IsDarkMode)
|
||||
{
|
||||
downloadingColor = Theme.PaletteDark.Success.ToString(MudBlazor.Utilities.MudColorOutputFormats.RGBA);
|
||||
haveColor = Theme.PaletteDark.Info.ToString(MudBlazor.Utilities.MudColorOutputFormats.RGBA);
|
||||
borderColor = Theme.PaletteDark.White.ToString(MudBlazor.Utilities.MudColorOutputFormats.RGBA);
|
||||
}
|
||||
else
|
||||
{
|
||||
downloadingColor = Theme.PaletteLight.Success.ToString(MudBlazor.Utilities.MudColorOutputFormats.RGBA);
|
||||
haveColor = Theme.PaletteLight.Info.ToString(MudBlazor.Utilities.MudColorOutputFormats.RGBA);
|
||||
borderColor = Theme.PaletteLight.Black.ToString(MudBlazor.Utilities.MudColorOutputFormats.RGBA);
|
||||
}
|
||||
await JSRuntime.RenderPiecesBar("progress", Hash, Pieces.Select(s => (int)s).ToArray(), downloadingColor, haveColor, borderColor);
|
||||
}
|
||||
|
||||
ResizeOptions IBrowserViewportObserver.ResizeOptions { get; } = new()
|
||||
{
|
||||
ReportRate = 50,
|
||||
NotifyOnBreakpointOnly = false
|
||||
};
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
await BrowserViewportService.SubscribeAsync(this, fireImmediately: true);
|
||||
}
|
||||
|
||||
await base.OnAfterRenderAsync(firstRender);
|
||||
}
|
||||
|
||||
public async Task NotifyBrowserViewportChangeAsync(BrowserViewportEventArgs browserViewportEventArgs)
|
||||
{
|
||||
await RenderPiecesBar();
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
protected virtual async Task DisposeAsync(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
await BrowserViewportService.UnsubscribeAsync(this);
|
||||
}
|
||||
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
await DisposeAsync(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
218
Lantean.QBTMud/Components/TorrentActions.razor
Normal file
218
Lantean.QBTMud/Components/TorrentActions.razor
Normal file
@@ -0,0 +1,218 @@
|
||||
@if (RenderType == RenderType.Toolbar)
|
||||
{
|
||||
<MudToolBar Dense="true" Gutters="false" WrapContent="true">
|
||||
@ToolbarContent
|
||||
</MudToolBar>
|
||||
}
|
||||
else if (RenderType == RenderType.ToolbarContents)
|
||||
{
|
||||
@ToolbarContent
|
||||
}
|
||||
else if (RenderType == RenderType.MixedToolbar)
|
||||
{
|
||||
<MudToolBar Dense="true" Gutters="false" WrapContent="true">
|
||||
@MixedToolbarContent
|
||||
</MudToolBar>
|
||||
}
|
||||
else if (RenderType == RenderType.MixedToolbarContents)
|
||||
{
|
||||
@MixedToolbarContent
|
||||
}
|
||||
else if (RenderType == RenderType.InitialIconsOnly)
|
||||
{
|
||||
@foreach (var action in Actions.Take(5))
|
||||
{
|
||||
@if (action.SeparatorBefore)
|
||||
{
|
||||
<MudDivider Vertical="true" />
|
||||
}
|
||||
|
||||
<MudIconButton title="@action.Text" Icon="@action.Icon" Color="action.Color" OnClick="action.Callback" Disabled="Disabled" />
|
||||
}
|
||||
|
||||
@Menu(Actions.Skip(5))
|
||||
}
|
||||
else if (RenderType == RenderType.Children)
|
||||
{
|
||||
var parent = Actions.FirstOrDefault(a => a.Name == ParentAction?.Name);
|
||||
if (parent is not null)
|
||||
{
|
||||
<MudList Class="unselectable" T="string">
|
||||
@foreach (var action in parent.Children)
|
||||
{
|
||||
@if (action.SeparatorBefore)
|
||||
{
|
||||
<MudDivider />
|
||||
}
|
||||
|
||||
<MudListItem Icon="@action.Icon" IconColor="action.Color" OnClick="action.Callback" Disabled="Disabled">@action.Text</MudListItem>
|
||||
}
|
||||
</MudList>
|
||||
}
|
||||
}
|
||||
else if (RenderType == RenderType.Menu)
|
||||
{
|
||||
@Menu(Actions)
|
||||
}
|
||||
else if (RenderType == RenderType.MenuWithoutActivator)
|
||||
{
|
||||
<MudMenu ListClass="unselectable" Dense="true" AnchorOrigin="Origin.BottomLeft" TransformOrigin="Origin.TopLeft" @ref="ActionsMenu" Disabled="@(!Hashes.Any())" Style="display: none" PositionAtCursor="true" OpenChanged="ActionsMenuOpenChanged" PopoverClass="unselectable">
|
||||
<ActivatorContent>
|
||||
|
||||
</ActivatorContent>
|
||||
<ChildContent>
|
||||
@if (PrimaryHash is not null)
|
||||
{
|
||||
<MudMenuItem Icon="@Icons.Material.Outlined.Info" IconColor="Color.Inherit" Disabled="Disabled" Href="@("/details/" + PrimaryHash)">View torrent details</MudMenuItem>
|
||||
<MudDivider />
|
||||
}
|
||||
@MenuContents(Actions)
|
||||
</ChildContent>
|
||||
</MudMenu>
|
||||
<MudOverlay LockScroll="true" AutoClose="true" Visible="OverlayVisible" VisibleChanged="OverlayVisibleChanged">
|
||||
<div style="width: 100%; height: 100%" @oncontextmenu="@(e => OverlayVisible = false)" @oncontextmenu:preventDefault></div>
|
||||
</MudOverlay>
|
||||
}
|
||||
else if (RenderType == RenderType.MenuItems)
|
||||
{
|
||||
@MenuContents(Actions)
|
||||
}
|
||||
|
||||
@code {
|
||||
private RenderFragment ToolbarContent
|
||||
{
|
||||
get
|
||||
{
|
||||
return __builder =>
|
||||
{
|
||||
foreach (var action in Actions)
|
||||
{
|
||||
if (action.SeparatorBefore)
|
||||
{
|
||||
<MudDivider Vertical="true" />
|
||||
}
|
||||
|
||||
if (!action.Children.Any())
|
||||
{
|
||||
if (action.Icon is null)
|
||||
{
|
||||
<MudButton Color="action.Color" OnClick="action.Callback">@action.Text</MudButton>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudIconButton title="@action.Text" Icon="@action.Icon" Color="action.Color" OnClick="action.Callback" Disabled="Disabled" />
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudMenu Icon="@action.Icon" IconColor="@action.Color" Label="@action.Text" title="@action.Text" AnchorOrigin="Origin.BottomLeft" TransformOrigin="Origin.TopLeft">
|
||||
@foreach (var childItem in action.Children)
|
||||
{
|
||||
@ChildItem(childItem)
|
||||
}
|
||||
</MudMenu>
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private RenderFragment MixedToolbarContent
|
||||
{
|
||||
get
|
||||
{
|
||||
return __builder =>
|
||||
{
|
||||
foreach (var action in Actions)
|
||||
{
|
||||
if (action.SeparatorBefore)
|
||||
{
|
||||
<MudDivider Vertical="true" />
|
||||
}
|
||||
|
||||
if (!action.Children.Any())
|
||||
{
|
||||
if (action.Icon is null)
|
||||
{
|
||||
<MudButton Color="action.Color" OnClick="action.Callback" Disabled="Disabled">@action.Text</MudButton>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudIconButton title="@action.Text" Icon="@action.Icon" Color="action.Color" OnClick="action.Callback" Disabled="Disabled" />
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudMenu Label="@action.Text" title="@action.Text" AnchorOrigin="Origin.BottomLeft" TransformOrigin="Origin.TopLeft" EndIcon="@Icons.Material.Filled.ArrowDropDown">
|
||||
@foreach (var childItem in action.Children)
|
||||
{
|
||||
@ChildItem(childItem)
|
||||
}
|
||||
</MudMenu>
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private RenderFragment ChildItem(UIAction action)
|
||||
{
|
||||
return __builder =>
|
||||
{
|
||||
if (action.SeparatorBefore)
|
||||
{
|
||||
<MudDivider />
|
||||
}
|
||||
|
||||
<MudMenuItem Icon="@action.Icon" IconColor="action.Color" OnClick="action.Callback" Disabled="Disabled">@action.Text</MudMenuItem>
|
||||
};
|
||||
}
|
||||
|
||||
private RenderFragment Menu(IEnumerable<UIAction> actions)
|
||||
{
|
||||
return __builder =>
|
||||
{
|
||||
<MudMenu ListClass="unselectable" Dense="true" AnchorOrigin="Origin.BottomLeft" TransformOrigin="Origin.TopLeft" Label="Actions" EndIcon="@Icons.Material.Filled.ArrowDropDown" @ref="ActionsMenu" Disabled="@(!Hashes.Any())" ActivationEvent="MouseEvent.LeftClick">
|
||||
@MenuContents(actions)
|
||||
</MudMenu>
|
||||
};
|
||||
}
|
||||
|
||||
private RenderFragment MenuContents(IEnumerable<UIAction> actions)
|
||||
{
|
||||
return __builder =>
|
||||
{
|
||||
foreach (var action in actions)
|
||||
{
|
||||
if (action.SeparatorBefore)
|
||||
{
|
||||
<MudDivider />
|
||||
}
|
||||
|
||||
if (!action.Children.Any())
|
||||
{
|
||||
<MudMenuItem Icon="@action.Icon" IconColor="action.Color" OnClick="action.Callback" Disabled="Disabled">
|
||||
@action.Text
|
||||
</MudMenuItem>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudMenuItem Icon="@action.Icon" IconColor="action.Color" OnClick="@(t => SubMenuTouch(action))">
|
||||
<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
|
||||
</ActivatorContent>
|
||||
|
||||
<ChildContent>
|
||||
@foreach (var childItem in action.Children)
|
||||
{
|
||||
@ChildItem(childItem)
|
||||
}
|
||||
</ChildContent>
|
||||
</MudMenu>
|
||||
</MudMenuItem>
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
719
Lantean.QBTMud/Components/TorrentActions.razor.cs
Normal file
719
Lantean.QBTMud/Components/TorrentActions.razor.cs
Normal file
@@ -0,0 +1,719 @@
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBTMud.Components.Dialogs;
|
||||
using Lantean.QBTMud.Helpers;
|
||||
using Lantean.QBTMud.Interop;
|
||||
using Lantean.QBTMud.Models;
|
||||
using Lantean.QBTMud.Services;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.JSInterop;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMud.Components
|
||||
{
|
||||
public partial class TorrentActions : IAsyncDisposable
|
||||
{
|
||||
private const int _defaultVersion = 5;
|
||||
|
||||
private bool _disposedValue;
|
||||
private int? _version;
|
||||
|
||||
private List<UIAction>? _actions;
|
||||
|
||||
[Inject]
|
||||
public IApiClient ApiClient { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
public NavigationManager NavigationManager { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
public IDialogService DialogService { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
public ISnackbar Snackbar { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
public IDataManager DataManager { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
public IJSRuntime JSRuntime { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected IKeyboardService KeyboardService { get; set; } = default!;
|
||||
|
||||
[CascadingParameter(Name = "Version")]
|
||||
public string? Version { get; set; }
|
||||
|
||||
[Parameter]
|
||||
[EditorRequired]
|
||||
public IEnumerable<string> Hashes { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public string? PrimaryHash { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true this component will render as a <see cref="MudToolBar"/> otherwise will render as a <see cref="MudMenu"/>.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderType RenderType { get; set; }
|
||||
|
||||
[Parameter, EditorRequired]
|
||||
public Dictionary<string, Torrent> Torrents { get; set; } = default!;
|
||||
|
||||
[Parameter, EditorRequired]
|
||||
public QBitTorrentClient.Models.Preferences? Preferences { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public MudDialogInstance? MudDialog { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public UIAction? ParentAction { get; set; }
|
||||
|
||||
public MudMenu? ActionsMenu { get; set; }
|
||||
|
||||
protected bool Disabled => !Hashes.Any();
|
||||
|
||||
protected bool OverlayVisible { get; set; }
|
||||
|
||||
protected int MajorVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_version is not null)
|
||||
{
|
||||
return _version.Value;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(Version))
|
||||
{
|
||||
return _defaultVersion;
|
||||
}
|
||||
|
||||
if (!System.Version.TryParse(Version.Replace("v", ""), out var version))
|
||||
{
|
||||
return _defaultVersion;
|
||||
}
|
||||
|
||||
_version = version.Major;
|
||||
|
||||
return _version.Value;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
_actions =
|
||||
[
|
||||
new("start", "Start", Icons.Material.Filled.PlayArrow, Color.Success, CreateCallback(Resume)),
|
||||
new("pause", "Pause", Icons.Material.Filled.Pause, Color.Warning, CreateCallback(Pause)),
|
||||
new("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),
|
||||
new("rename", "Rename", Icons.Material.Filled.DriveFileRenameOutline, Color.Info, CreateCallback(Rename)),
|
||||
new("renameFiles", "Rename files", Icons.Material.Filled.DriveFileRenameOutline, Color.Warning, CreateCallback(Rename)),
|
||||
new("category", "Category", Icons.Material.Filled.List, Color.Info, CreateCallback(ShowCategories)),
|
||||
new("tags", "Tags", Icons.Material.Filled.Label, Color.Info, CreateCallback(ShowTags)),
|
||||
new("autoTorrentManagement", "Automatic Torrent Management", Icons.Material.Filled.Check, Color.Info, CreateCallback(ToggleAutoTMM)),
|
||||
new("downloadLimit", "Limit download rate", Icons.Material.Filled.KeyboardDoubleArrowDown, Color.Success, CreateCallback(LimitDownloadRate), separatorBefore: true),
|
||||
new("uploadLimit", "Limit upload rate", Icons.Material.Filled.KeyboardDoubleArrowUp, Color.Warning, CreateCallback(LimitUploadRate)),
|
||||
new("shareRatio", "Limit share ratio", Icons.Material.Filled.Percent, Color.Info, CreateCallback(LimitShareRatio)),
|
||||
new("superSeeding", "Super seeding mode", Icons.Material.Filled.Check, Color.Info, CreateCallback(ToggleSuperSeeding)),
|
||||
new("sequentialDownload", "Download in sequential order", Icons.Material.Filled.Check, Color.Info, CreateCallback(DownloadSequential), separatorBefore: true),
|
||||
new("firstLastPiecePrio", "Download first and last pieces first", Icons.Material.Filled.Check, Color.Info, CreateCallback(DownloadFirstLast)),
|
||||
new("forceRecheck", "Force recheck", Icons.Material.Filled.Loop, Color.Info, CreateCallback(ForceRecheck), separatorBefore: true),
|
||||
new("forceReannounce", "Force reannounce", Icons.Material.Filled.BroadcastOnHome, Color.Info, CreateCallback(ForceReannounce)),
|
||||
new("queue", "Queue", Icons.Material.Filled.Queue, Color.Transparent,
|
||||
[
|
||||
new("queueTop", "Move to top", Icons.Material.Filled.VerticalAlignTop, Color.Inherit, CreateCallback(MoveToTop)),
|
||||
new("queueUp", "Move up", Icons.Material.Filled.ArrowUpward, Color.Inherit, CreateCallback(MoveUp)),
|
||||
new("queueDown", "Move down", Icons.Material.Filled.ArrowDownward, Color.Inherit, CreateCallback(MoveDown)),
|
||||
new("queueBottom", "Move to bottom", Icons.Material.Filled.VerticalAlignBottom, Color.Inherit, CreateCallback(MoveToBottom)),
|
||||
], separatorBefore: true),
|
||||
new("copy", "Copy", Icons.Material.Filled.FolderCopy, Color.Info,
|
||||
[
|
||||
new("copyName", "Name", Icons.Material.Filled.TextFields, Color.Info, CreateCallback(() => Copy(t => t.Name))),
|
||||
new("copyHashv1", "Info hash v1", Icons.Material.Filled.Tag, Color.Info, CreateCallback(() => Copy(t => t.InfoHashV1))),
|
||||
new("copyHashv2", "Info hash v2", Icons.Material.Filled.Tag, Color.Info, CreateCallback(() => Copy(t => t.InfoHashV2))),
|
||||
new("copyMagnet", "Magnet link", Icons.Material.Filled.TextFields, Color.Info, CreateCallback(() => Copy(t => t.MagnetUri))),
|
||||
new("copyId", "Torrent ID", Icons.Material.Filled.TextFields, Color.Info, CreateCallback(() => Copy(t => t.Hash))),
|
||||
]),
|
||||
new("export", "Export", Icons.Material.Filled.SaveAlt, Color.Info, CreateCallback(Export)),
|
||||
];
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
await KeyboardService.RegisterKeypressEvent("Delete", k => Remove());
|
||||
}
|
||||
}
|
||||
|
||||
public int CalculateMenuHeight()
|
||||
{
|
||||
var visibleActions = GetActions();
|
||||
|
||||
var actionCount = visibleActions.Count();
|
||||
var separatorCount = visibleActions.Count(c => c.SeparatorBefore);
|
||||
|
||||
return actionCount * 36 + separatorCount * 1;
|
||||
}
|
||||
|
||||
protected async Task OverlayVisibleChanged(bool value)
|
||||
{
|
||||
OverlayVisible = value;
|
||||
if (!value && ActionsMenu is not null)
|
||||
{
|
||||
await ActionsMenu.CloseMenuAsync();
|
||||
}
|
||||
}
|
||||
|
||||
protected void ActionsMenuOpenChanged(bool value)
|
||||
{
|
||||
OverlayVisible = value;
|
||||
}
|
||||
|
||||
protected async Task Pause()
|
||||
{
|
||||
await ApiClient.PauseTorrents(Hashes);
|
||||
Snackbar.Add("Torrent paused.");
|
||||
}
|
||||
|
||||
protected async Task Resume()
|
||||
{
|
||||
await ApiClient.ResumeTorrents(Hashes);
|
||||
Snackbar.Add("Torrent resumed.");
|
||||
}
|
||||
|
||||
protected async Task ForceStart()
|
||||
{
|
||||
await ApiClient.SetForceStart(true, null, Hashes.ToArray());
|
||||
Snackbar.Add("Torrent force started.");
|
||||
}
|
||||
|
||||
protected async Task Remove()
|
||||
{
|
||||
var deleted = await DialogService.InvokeDeleteTorrentDialog(ApiClient, Hashes.ToArray());
|
||||
|
||||
if (deleted)
|
||||
{
|
||||
NavigationManager.NavigateTo("/");
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task SetLocation()
|
||||
{
|
||||
string? savePath = null;
|
||||
if (Hashes.Any() && Torrents.TryGetValue(Hashes.First(), out var torrent))
|
||||
{
|
||||
savePath = torrent.SavePath;
|
||||
}
|
||||
|
||||
await DialogService.InvokeStringFieldDialog("Set Location", "Location", savePath, v => ApiClient.SetTorrentLocation(v, null, Hashes.ToArray()));
|
||||
}
|
||||
|
||||
protected async Task Rename()
|
||||
{
|
||||
string? name = null;
|
||||
string hash = Hashes.First();
|
||||
if (Hashes.Any() && Torrents.TryGetValue(hash, out var torrent))
|
||||
{
|
||||
name = torrent.Name;
|
||||
}
|
||||
await DialogService.InvokeStringFieldDialog("Rename", "Name", name, v => ApiClient.SetTorrentName(v, hash));
|
||||
}
|
||||
|
||||
protected async Task RenameFiles()
|
||||
{
|
||||
await DialogService.InvokeRenameFilesDialog(Hashes.First());
|
||||
}
|
||||
|
||||
protected async Task SetCategory(string category)
|
||||
{
|
||||
await ApiClient.SetTorrentCategory(category, null, Hashes.ToArray());
|
||||
}
|
||||
|
||||
protected async Task ToggleAutoTMM()
|
||||
{
|
||||
var torrents = GetTorrents();
|
||||
|
||||
await ApiClient.SetAutomaticTorrentManagement(false, null, torrents.Where(t => t.AutomaticTorrentManagement).Select(t => t.Hash).ToArray());
|
||||
await ApiClient.SetAutomaticTorrentManagement(true, null, torrents.Where(t => !t.AutomaticTorrentManagement).Select(t => t.Hash).ToArray());
|
||||
}
|
||||
|
||||
protected async Task LimitDownloadRate()
|
||||
{
|
||||
long downloadLimit = -1;
|
||||
string hash = Hashes.First();
|
||||
if (Hashes.Any() && Torrents.TryGetValue(hash, out var torrent))
|
||||
{
|
||||
downloadLimit = torrent.DownloadLimit;
|
||||
}
|
||||
|
||||
await DialogService.InvokeDownloadRateDialog(ApiClient, downloadLimit, Hashes);
|
||||
}
|
||||
|
||||
protected async Task LimitUploadRate()
|
||||
{
|
||||
long uploadLimit = -1;
|
||||
string hash = Hashes.First();
|
||||
if (Hashes.Any() && Torrents.TryGetValue(hash, out var torrent))
|
||||
{
|
||||
uploadLimit = torrent.UploadLimit;
|
||||
}
|
||||
|
||||
await DialogService.InvokeUploadRateDialog(ApiClient, uploadLimit, Hashes);
|
||||
}
|
||||
|
||||
protected async Task LimitShareRatio()
|
||||
{
|
||||
var torrents = new List<Torrent>();
|
||||
foreach (var hash in Hashes)
|
||||
{
|
||||
if (Torrents.TryGetValue(hash, out var torrent))
|
||||
{
|
||||
torrents.Add(torrent);
|
||||
}
|
||||
}
|
||||
|
||||
await DialogService.InvokeShareRatioDialog(ApiClient, torrents);
|
||||
}
|
||||
|
||||
protected async Task ToggleSuperSeeding()
|
||||
{
|
||||
var torrents = GetTorrents();
|
||||
|
||||
await ApiClient.SetSuperSeeding(false, null, torrents.Where(t => t.SuperSeeding).Select(t => t.Hash).ToArray());
|
||||
await ApiClient.SetSuperSeeding(true, null, torrents.Where(t => !t.SuperSeeding).Select(t => t.Hash).ToArray());
|
||||
}
|
||||
|
||||
protected async Task ForceRecheck()
|
||||
{
|
||||
await ApiClient.RecheckTorrents(null, Hashes.ToArray());
|
||||
}
|
||||
|
||||
protected async Task ForceReannounce()
|
||||
{
|
||||
await ApiClient.ReannounceTorrents(null, Hashes.ToArray());
|
||||
}
|
||||
|
||||
protected async Task MoveToTop()
|
||||
{
|
||||
await ApiClient.MaximalTorrentPriority(null, Hashes.ToArray());
|
||||
}
|
||||
|
||||
protected async Task MoveUp()
|
||||
{
|
||||
await ApiClient.IncreaseTorrentPriority(null, Hashes.ToArray());
|
||||
}
|
||||
|
||||
protected async Task MoveDown()
|
||||
{
|
||||
await ApiClient.DecreaseTorrentPriority(null, Hashes.ToArray());
|
||||
}
|
||||
|
||||
protected async Task MoveToBottom()
|
||||
{
|
||||
await ApiClient.MinimalTorrentPriority(null, Hashes.ToArray());
|
||||
}
|
||||
|
||||
protected async Task Copy(string value)
|
||||
{
|
||||
await JSRuntime.WriteToClipboard(value);
|
||||
}
|
||||
|
||||
protected async Task Copy(Func<Torrent, object?> selector)
|
||||
{
|
||||
await Copy(string.Join(Environment.NewLine, GetTorrents().Select(selector)));
|
||||
if (ActionsMenu is not null)
|
||||
{
|
||||
await ActionsMenu.CloseMenuAsync();
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task Export()
|
||||
{
|
||||
foreach (var torrent in GetTorrents())
|
||||
{
|
||||
var url = await ApiClient.GetExportUrl(torrent.Hash);
|
||||
await JSRuntime.FileDownload(url, $"{torrent.Name}.torrent");
|
||||
await Task.Delay(200);
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task ShowTags()
|
||||
{
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
{ nameof(ManageTagsDialog.Hashes), Hashes }
|
||||
};
|
||||
|
||||
await DialogService.ShowAsync<ManageTagsDialog>("Manage Torrent Tags", parameters, DialogHelper.FormDialogOptions);
|
||||
}
|
||||
|
||||
protected async Task ShowCategories()
|
||||
{
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
{ nameof(ManageCategoriesDialog.Hashes), Hashes }
|
||||
};
|
||||
|
||||
await DialogService.ShowAsync<ManageCategoriesDialog>("Manage Torrent Categories", parameters, DialogHelper.FormDialogOptions);
|
||||
}
|
||||
|
||||
protected async Task DownloadSequential()
|
||||
{
|
||||
await ApiClient.ToggleSequentialDownload(null, Hashes.ToArray());
|
||||
}
|
||||
|
||||
protected async Task DownloadFirstLast()
|
||||
{
|
||||
await ApiClient.SetFirstLastPiecePriority(null, Hashes.ToArray());
|
||||
}
|
||||
|
||||
protected async Task SubMenuTouch(UIAction action)
|
||||
{
|
||||
await DialogService.ShowSubMenu(Hashes, action, Torrents, Preferences);
|
||||
}
|
||||
|
||||
private IEnumerable<Torrent> GetTorrents()
|
||||
{
|
||||
foreach (var hash in Hashes)
|
||||
{
|
||||
if (Torrents.TryGetValue(hash, out var torrent))
|
||||
{
|
||||
yield return torrent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<UIAction> Actions => GetActions();
|
||||
|
||||
private IEnumerable<UIAction> GetActions()
|
||||
{
|
||||
var allAreSequentialDownload = true;
|
||||
var thereAreSequentialDownload = false;
|
||||
var allAreFirstLastPiecePrio = true;
|
||||
var thereAreFirstLastPiecePrio = false;
|
||||
var allAreDownloaded = true;
|
||||
var allArePaused = true;
|
||||
var thereArePaused = false;
|
||||
var allAreForceStart = true;
|
||||
var thereAreForceStart = false;
|
||||
var allAreSuperSeeding = true;
|
||||
var allAreAutoTmm = true;
|
||||
var thereAreAutoTmm = false;
|
||||
|
||||
Torrent? firstTorrent = null;
|
||||
foreach (var torrent in GetTorrents())
|
||||
{
|
||||
firstTorrent ??= torrent;
|
||||
if (!torrent.SequentialDownload)
|
||||
{
|
||||
allAreSequentialDownload = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
thereAreSequentialDownload = true;
|
||||
}
|
||||
|
||||
if (!torrent.FirstLastPiecePriority)
|
||||
{
|
||||
allAreFirstLastPiecePrio = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
thereAreFirstLastPiecePrio = true;
|
||||
}
|
||||
|
||||
if (torrent.Progress != 1.0) // not downloaded
|
||||
{
|
||||
allAreDownloaded = false;
|
||||
}
|
||||
else if (!torrent.SuperSeeding)
|
||||
{
|
||||
allAreSuperSeeding = false;
|
||||
}
|
||||
|
||||
if (MajorVersion < 5)
|
||||
{
|
||||
if (torrent.State != "pausedUP" && torrent.State != "pausedDL")
|
||||
{
|
||||
allArePaused = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
thereArePaused = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (torrent.State != "stoppedUP" && torrent.State != "stoppedDL")
|
||||
{
|
||||
allArePaused = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
thereArePaused = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!torrent.ForceStart)
|
||||
{
|
||||
allAreForceStart = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
thereAreForceStart = true;
|
||||
}
|
||||
|
||||
if (torrent.AutomaticTorrentManagement)
|
||||
{
|
||||
thereAreAutoTmm = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
allAreAutoTmm = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool showSequentialDownload = true;
|
||||
if (!allAreSequentialDownload && thereAreSequentialDownload)
|
||||
{
|
||||
showSequentialDownload = false;
|
||||
}
|
||||
|
||||
bool showAreFirstLastPiecePrio = true;
|
||||
if (!allAreFirstLastPiecePrio && thereAreFirstLastPiecePrio)
|
||||
{
|
||||
showAreFirstLastPiecePrio = false;
|
||||
}
|
||||
|
||||
var actionStates = new Dictionary<string, ActionState>();
|
||||
|
||||
var showRenameFiles = Hashes.Count() == 1 && firstTorrent!.MetaDownloaded();
|
||||
if (!showRenameFiles)
|
||||
{
|
||||
actionStates["renameFiles"] = ActionState.Hidden;
|
||||
}
|
||||
|
||||
if (allAreDownloaded)
|
||||
{
|
||||
actionStates["downloadLimit"] = ActionState.Hidden;
|
||||
actionStates["uploadLimit"] = ActionState.HasSeperator;
|
||||
actionStates["sequentialDownload"] = ActionState.Hidden;
|
||||
actionStates["firstLastPiecePrio"] = ActionState.Hidden;
|
||||
actionStates["superSeeding"] = new ActionState { IsChecked = allAreSuperSeeding };
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!showSequentialDownload && showAreFirstLastPiecePrio)
|
||||
{
|
||||
actionStates["firstLastPiecePrio"] = ActionState.HasSeperator;
|
||||
}
|
||||
|
||||
if (!showSequentialDownload)
|
||||
{
|
||||
actionStates["sequentialDownload"] = ActionState.Hidden;
|
||||
}
|
||||
|
||||
if (!showAreFirstLastPiecePrio)
|
||||
{
|
||||
actionStates["firstLastPiecePrio"] = ActionState.Hidden;
|
||||
}
|
||||
|
||||
if (!actionStates.TryGetValue("sequentialDownload", out var sequentialDownload))
|
||||
{
|
||||
actionStates["sequentialDownload"] = new ActionState { IsChecked = allAreSequentialDownload };
|
||||
}
|
||||
else
|
||||
{
|
||||
sequentialDownload.IsChecked = allAreSequentialDownload;
|
||||
}
|
||||
|
||||
if (!actionStates.TryGetValue("firstLastPiecePrio", out var firstLastPiecePrio))
|
||||
{
|
||||
actionStates["firstLastPiecePrio"] = new ActionState { IsChecked = allAreFirstLastPiecePrio };
|
||||
}
|
||||
else
|
||||
{
|
||||
firstLastPiecePrio.IsChecked = allAreFirstLastPiecePrio;
|
||||
}
|
||||
|
||||
actionStates["superSeeding"] = ActionState.Hidden;
|
||||
}
|
||||
|
||||
if (allArePaused)
|
||||
{
|
||||
actionStates["pause"] = ActionState.Hidden;
|
||||
}
|
||||
else if (allAreForceStart)
|
||||
{
|
||||
actionStates["forceStart"] = ActionState.Hidden;
|
||||
}
|
||||
else if (!thereArePaused && !thereAreForceStart)
|
||||
{
|
||||
actionStates["start"] = ActionState.Hidden;
|
||||
}
|
||||
|
||||
if (MajorVersion >= 5)
|
||||
{
|
||||
if (actionStates.ContainsKey("start"))
|
||||
{
|
||||
actionStates["start"].TextOverride = "Start";
|
||||
}
|
||||
else
|
||||
{
|
||||
actionStates["start"] = new ActionState { TextOverride = "Start" };
|
||||
}
|
||||
|
||||
if (actionStates.ContainsKey("pause"))
|
||||
{
|
||||
actionStates["pause"].TextOverride = "Stop";
|
||||
}
|
||||
else
|
||||
{
|
||||
actionStates["pause"] = new ActionState { TextOverride = "Stop" };
|
||||
}
|
||||
}
|
||||
|
||||
if (!allAreAutoTmm && thereAreAutoTmm)
|
||||
{
|
||||
actionStates["autoTorrentManagement"] = ActionState.Hidden;
|
||||
}
|
||||
else
|
||||
{
|
||||
actionStates["autoTorrentManagement"] = new ActionState { IsChecked = allAreAutoTmm };
|
||||
}
|
||||
|
||||
if (Preferences?.QueueingEnabled == false)
|
||||
{
|
||||
actionStates["queue"] = ActionState.Hidden;
|
||||
}
|
||||
|
||||
return Filter(actionStates);
|
||||
}
|
||||
|
||||
private IEnumerable<UIAction> Filter(Dictionary<string, ActionState> actionStates)
|
||||
{
|
||||
if (_actions is null)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
foreach (var action in _actions)
|
||||
{
|
||||
if (!actionStates.TryGetValue(action.Name, out var actionState))
|
||||
{
|
||||
yield return action;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (actionState.Show is null || actionState.Show.Value)
|
||||
{
|
||||
var act = action with { };
|
||||
if (actionState.HasSeparator.HasValue)
|
||||
{
|
||||
act.SeparatorBefore = actionState.HasSeparator.Value;
|
||||
}
|
||||
if (actionState.IsChecked.HasValue)
|
||||
{
|
||||
act.IsChecked = actionState.IsChecked.Value;
|
||||
}
|
||||
if (actionState.TextOverride is not null)
|
||||
{
|
||||
act.Text = actionState.TextOverride;
|
||||
}
|
||||
|
||||
yield return act;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ActionState
|
||||
{
|
||||
public bool? Show { get; set; }
|
||||
|
||||
public bool? HasSeparator { get; set; }
|
||||
|
||||
public bool? IsChecked { get; set; }
|
||||
|
||||
public string? TextOverride { get; set; }
|
||||
|
||||
public static readonly ActionState Hidden = new() { Show = false };
|
||||
|
||||
public static readonly ActionState HasSeperator = new() { HasSeparator = true };
|
||||
}
|
||||
|
||||
private EventCallback CreateCallback(Func<Task> action)
|
||||
{
|
||||
if (MudDialog is not null)
|
||||
{
|
||||
return EventCallback.Factory.Create(this, async () =>
|
||||
{
|
||||
await action();
|
||||
MudDialog?.Close();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
return EventCallback.Factory.Create(this, action);
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
await DisposeAsync(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual async Task DisposeAsync(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
await KeyboardService.UnregisterKeypressEvent("Delete");
|
||||
}
|
||||
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum RenderType
|
||||
{
|
||||
/// <summary>
|
||||
/// Renders toolbar contents without the <see cref="MudToolBar"/> wrapper.
|
||||
/// </summary>
|
||||
ToolbarContents,
|
||||
|
||||
/// <summary>
|
||||
/// Renders a <see cref="MudToolBar"/>.
|
||||
/// </summary>
|
||||
Toolbar,
|
||||
|
||||
/// <summary>
|
||||
/// Renders a <see cref="MudMenu"/>.
|
||||
/// </summary>
|
||||
Menu,
|
||||
|
||||
/// <summary>
|
||||
/// Renders a <see cref="MudToolBar"/> with <see cref="MudIconButton"/> for basic actions and a <see cref="MudMenu"/> for actions with children.
|
||||
/// </summary>
|
||||
MixedToolbarContents,
|
||||
|
||||
/// <summary>
|
||||
/// Renders toolbar contents without the <see cref="MudToolBar"/> wrapper with <see cref="MudIconButton"/> for basic actions and a <see cref="MudMenu"/> for actions with children.
|
||||
/// </summary>
|
||||
MixedToolbar,
|
||||
|
||||
InitialIconsOnly,
|
||||
|
||||
Children,
|
||||
|
||||
MenuWithoutActivator,
|
||||
|
||||
MenuItems,
|
||||
}
|
||||
}
|
||||
23
Lantean.QBTMud/Components/TorrentInfo.razor
Normal file
23
Lantean.QBTMud/Components/TorrentInfo.razor
Normal file
@@ -0,0 +1,23 @@
|
||||
@if (Torrent is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
<MudToolBar Dense="true" Gutters="false" WrapContent="true">
|
||||
@{
|
||||
var (icon, color) = DisplayHelpers.GetStateIcon(Torrent.State);
|
||||
}
|
||||
<MudIcon Color="@color" Icon="@icon" />
|
||||
<MudText Class="pl-5 no-wrap">@Torrent.Name</MudText>
|
||||
<MudDivider Vertical="true" />
|
||||
<MudText Class="pl-5 no-wrap">@DisplayHelpers.Size(Torrent.Size)</MudText>
|
||||
<MudDivider Vertical="true" />
|
||||
@{
|
||||
var value = Torrent.Progress;
|
||||
var progressColor = value < 1 ? Color.Success : Color.Info;
|
||||
<MudProgressLinear title="Progress" Color="@(progressColor)" Value="@((value) * 100)" Class="progress-expand" Size="Size.Large">
|
||||
@DisplayHelpers.Percentage(value)
|
||||
</MudProgressLinear>
|
||||
;
|
||||
}
|
||||
</MudToolBar>
|
||||
27
Lantean.QBTMud/Components/TorrentInfo.razor.cs
Normal file
27
Lantean.QBTMud/Components/TorrentInfo.razor.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using Lantean.QBTMud.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Lantean.QBTMud.Components
|
||||
{
|
||||
public partial class TorrentInfo
|
||||
{
|
||||
[Parameter]
|
||||
[EditorRequired]
|
||||
public string Hash { get; set; } = default!;
|
||||
|
||||
[CascadingParameter]
|
||||
public MainData MainData { get; set; } = default!;
|
||||
|
||||
protected Torrent? Torrent => GetTorrent();
|
||||
|
||||
private Torrent? GetTorrent()
|
||||
{
|
||||
if (Hash is null || !MainData.Torrents.TryGetValue(Hash, out var torrent))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return torrent;
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Lantean.QBTMud/Components/TorrentsListNav.razor
Normal file
18
Lantean.QBTMud/Components/TorrentsListNav.razor
Normal file
@@ -0,0 +1,18 @@
|
||||
<MudNavMenu>
|
||||
<MudNavLink Icon="@(Icons.Material.Outlined.NavigateBefore)" OnClick="NavigateBack">Back</MudNavLink>
|
||||
<MudDivider />
|
||||
@if (OrderedTorrents is null)
|
||||
{
|
||||
@for (var i = 0; i < 10; i++)
|
||||
{
|
||||
<MudSkeleton Animation="Animation.Pulse" Width="100%" Height="25px" />
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var torrent in OrderedTorrents)
|
||||
{
|
||||
<MudNavLink Href="@("/details/" + torrent.Hash)">@torrent.Name</MudNavLink>
|
||||
}
|
||||
}
|
||||
</MudNavMenu>
|
||||
44
Lantean.QBTMud/Components/TorrentsListNav.razor.cs
Normal file
44
Lantean.QBTMud/Components/TorrentsListNav.razor.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using Lantean.QBTMud.Models;
|
||||
using Lantean.QBTMud.Pages;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMud.Components
|
||||
{
|
||||
public partial class TorrentsListNav
|
||||
{
|
||||
[Inject]
|
||||
protected NavigationManager NavigationManager { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public IEnumerable<Torrent>? Torrents { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string? SelectedTorrent { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public SortDirection SortDirection { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string? SortColumn { get; set; }
|
||||
|
||||
protected IEnumerable<Torrent>? OrderedTorrents => GetOrderedTorrents();
|
||||
|
||||
private IEnumerable<Torrent>? GetOrderedTorrents()
|
||||
{
|
||||
if (Torrents is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var sortSelector = TorrentList.ColumnsDefinitions.Find(t => t.Id == SortColumn)?.SortSelector ?? (t => t.Name);
|
||||
|
||||
return Torrents.OrderByDirection(SortDirection, sortSelector);
|
||||
}
|
||||
|
||||
protected void NavigateBack()
|
||||
{
|
||||
NavigationManager.NavigateTo("/");
|
||||
}
|
||||
}
|
||||
}
|
||||
32
Lantean.QBTMud/Components/TrackersTab.razor
Normal file
32
Lantean.QBTMud/Components/TrackersTab.razor
Normal file
@@ -0,0 +1,32 @@
|
||||
<ContextMenu @ref="ContextMenu" Dense="true">
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.AddCircle" IconColor="Color.Info" OnClick="AddTracker">Add trackers</MudMenuItem>
|
||||
@if (ContextMenuItem is not null)
|
||||
{
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Edit" IconColor="Color.Info" OnClick="EditTrackerToolbar">Edit tracker URL</MudMenuItem>
|
||||
<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>
|
||||
|
||||
<MudToolBar Gutters="false" Dense="true">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.AddCircle" Color="Color.Info" OnClick="AddTracker">Add trackers</MudIconButton>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Edit" Color="Color.Info" OnClick="EditTrackerToolbar" Disabled="@(SelectedItem is null)">Edit tracker URL</MudIconButton>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Delete" Color="Color.Error" OnClick="RemoveTrackerToolbar" Disabled="@(SelectedItem is null)">Remove tracker</MudIconButton>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.FolderCopy" Color="Color.Info" OnClick="CopyTrackerUrlToolbar" Disabled="@(SelectedItem is null)">Copy tracker url</MudIconButton>
|
||||
<MudDivider Vertical="true" />
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.ViewColumn" Color="Color.Inherit" OnClick="ColumnOptions" title="Choose Columns" />
|
||||
</MudToolBar>
|
||||
|
||||
<DynamicTable @ref="Table"
|
||||
T="Lantean.QBitTorrentClient.Models.TorrentTracker"
|
||||
ColumnDefinitions="Columns"
|
||||
Items="Trackers"
|
||||
MultiSelection="false"
|
||||
SelectOnRowClick="false"
|
||||
PreSorted="true"
|
||||
SortDirectionChanged="SortDirectionChanged"
|
||||
SortColumnChanged="SortColumnChanged"
|
||||
OnTableDataLongPress="TableDataLongPress"
|
||||
OnTableDataContextMenu="TableDataContextMenu"
|
||||
SelectedItemChanged="SelectedItemChanged"
|
||||
Class="file-list" />
|
||||
304
Lantean.QBTMud/Components/TrackersTab.razor.cs
Normal file
304
Lantean.QBTMud/Components/TrackersTab.razor.cs
Normal file
@@ -0,0 +1,304 @@
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBitTorrentClient.Models;
|
||||
using Lantean.QBTMud.Components.UI;
|
||||
using Lantean.QBTMud.Helpers;
|
||||
using Lantean.QBTMud.Interop;
|
||||
using Lantean.QBTMud.Models;
|
||||
using Lantean.QBTMud.Services;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.JSInterop;
|
||||
using MudBlazor;
|
||||
using System.Net;
|
||||
|
||||
namespace Lantean.QBTMud.Components
|
||||
{
|
||||
public partial class TrackersTab : IAsyncDisposable
|
||||
{
|
||||
private readonly CancellationTokenSource _timerCancellationToken = new();
|
||||
private bool _disposedValue;
|
||||
|
||||
private string? _sortColumn;
|
||||
private SortDirection _sortDirection;
|
||||
|
||||
private const string _toolbar = nameof(_toolbar);
|
||||
private const string _context = nameof(_context);
|
||||
|
||||
[Parameter, EditorRequired]
|
||||
public string? Hash { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool Active { get; set; }
|
||||
|
||||
[CascadingParameter(Name = "RefreshInterval")]
|
||||
public int RefreshInterval { get; set; }
|
||||
|
||||
[Inject]
|
||||
protected IDialogService DialogService { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected IJSRuntime JSRuntime { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected IApiClient ApiClient { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected IDataManager DataManager { get; set; } = default!;
|
||||
|
||||
protected IReadOnlyList<TorrentTracker>? TrackerList { get; set; }
|
||||
|
||||
protected IEnumerable<TorrentTracker>? Trackers => GetTrackers();
|
||||
|
||||
protected TorrentTracker? ContextMenuItem { get; set; }
|
||||
|
||||
protected TorrentTracker? SelectedItem { get; set; }
|
||||
|
||||
protected ContextMenu? ContextMenu { get; set; }
|
||||
|
||||
protected DynamicTable<TorrentTracker>? Table { get; set; }
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (Hash is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Active)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TrackerList = await ApiClient.GetTorrentTrackers(Hash);
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
protected IEnumerable<TorrentTracker>? GetTrackers()
|
||||
{
|
||||
if (TrackerList is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var trackers = TrackerList.Where(t => !IsRealTracker(t)).ToList();
|
||||
trackers.AddRange(TrackerList.Where(IsRealTracker).OrderByDirection(_sortDirection, GetSortSelector()));
|
||||
|
||||
return trackers.AsReadOnly();
|
||||
}
|
||||
|
||||
private static bool IsRealTracker(TorrentTracker torrentTracker)
|
||||
{
|
||||
return !torrentTracker.Url.StartsWith("**");
|
||||
}
|
||||
|
||||
private Func<TorrentTracker, object?> GetSortSelector()
|
||||
{
|
||||
var sortSelector = ColumnsDefinitions.Find(c => c.Id == _sortColumn)?.SortSelector;
|
||||
|
||||
return sortSelector ?? (i => i.Url);
|
||||
}
|
||||
|
||||
protected void SortDirectionChanged(SortDirection sortDirection)
|
||||
{
|
||||
_sortDirection = sortDirection;
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
protected void SortColumnChanged(string column)
|
||||
{
|
||||
_sortColumn = column;
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
protected async Task ColumnOptions()
|
||||
{
|
||||
if (Table is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await Table.ShowColumnOptionsDialog();
|
||||
}
|
||||
|
||||
protected Task TableDataContextMenu(TableDataContextMenuEventArgs<TorrentTracker> eventArgs)
|
||||
{
|
||||
return ShowContextMenu(eventArgs.Item, eventArgs.MouseEventArgs);
|
||||
}
|
||||
|
||||
protected Task TableDataLongPress(TableDataLongPressEventArgs<TorrentTracker> eventArgs)
|
||||
{
|
||||
return ShowContextMenu(eventArgs.Item, eventArgs.LongPressEventArgs);
|
||||
}
|
||||
|
||||
protected async Task ShowContextMenu(TorrentTracker? tracker, EventArgs eventArgs)
|
||||
{
|
||||
if (tracker is not null && IsRealTracker(tracker))
|
||||
{
|
||||
ContextMenuItem = tracker;
|
||||
}
|
||||
else
|
||||
{
|
||||
ContextMenuItem = null;
|
||||
}
|
||||
|
||||
if (ContextMenu is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await ContextMenu.ToggleMenuAsync(eventArgs);
|
||||
}
|
||||
|
||||
protected void SelectedItemChanged(TorrentTracker torrentTracker)
|
||||
{
|
||||
SelectedItem = torrentTracker;
|
||||
}
|
||||
|
||||
protected async Task AddTracker()
|
||||
{
|
||||
if (Hash is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var trackers = await DialogService.ShowAddTrackersDialog();
|
||||
if (trackers is null || trackers.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await ApiClient.AddTrackersToTorrent(Hash, trackers);
|
||||
}
|
||||
|
||||
protected Task EditTrackerToolbar()
|
||||
{
|
||||
return EditTracker(SelectedItem);
|
||||
}
|
||||
|
||||
protected Task EditTrackerContextMenu()
|
||||
{
|
||||
return EditTracker(ContextMenuItem);
|
||||
}
|
||||
|
||||
protected async Task EditTracker(TorrentTracker? tracker)
|
||||
{
|
||||
if (Hash is null || tracker is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await DialogService.InvokeStringFieldDialog("Edit Tracker", "Tracker URL", tracker.Url, async (value) => await ApiClient.EditTracker(Hash, tracker.Url, value));
|
||||
}
|
||||
|
||||
protected Task RemoveTrackerToolbar()
|
||||
{
|
||||
return RemoveTracker(SelectedItem);
|
||||
}
|
||||
|
||||
protected Task RemoveTrackerContextMenu()
|
||||
{
|
||||
return RemoveTracker(ContextMenuItem);
|
||||
}
|
||||
|
||||
protected async Task RemoveTracker(TorrentTracker? tracker)
|
||||
{
|
||||
if (Hash is null || tracker is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await ApiClient.RemoveTrackers(Hash, [tracker.Url]);
|
||||
}
|
||||
|
||||
protected Task CopyTrackerUrlToolbar()
|
||||
{
|
||||
return CopyTrackerUrl(SelectedItem);
|
||||
}
|
||||
|
||||
protected Task CopyTrackerUrlContextMenu()
|
||||
{
|
||||
return CopyTrackerUrl(ContextMenuItem);
|
||||
}
|
||||
|
||||
protected async Task CopyTrackerUrl(TorrentTracker? tracker)
|
||||
{
|
||||
if (Hash is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (tracker is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await JSRuntime.WriteToClipboard(tracker.Url);
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
using (var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(RefreshInterval)))
|
||||
{
|
||||
while (!_timerCancellationToken.IsCancellationRequested && await timer.WaitForNextTickAsync())
|
||||
{
|
||||
if (Active && Hash is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
TrackerList = await ApiClient.GetTorrentTrackers(Hash);
|
||||
}
|
||||
catch (HttpRequestException exception) when (exception.StatusCode == HttpStatusCode.Forbidden || exception.StatusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
_timerCancellationToken.CancelIfNotDisposed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected IEnumerable<ColumnDefinition<TorrentTracker>> Columns => ColumnsDefinitions;
|
||||
|
||||
public static List<ColumnDefinition<TorrentTracker>> ColumnsDefinitions { get; } =
|
||||
[
|
||||
new ColumnDefinition<TorrentTracker>("Tier", w => w.Tier, w => w.Tier > 0 ? w.Tier.ToString() : ""),
|
||||
new ColumnDefinition<TorrentTracker>("URL", w => w.Url),
|
||||
new ColumnDefinition<TorrentTracker>("Status", w => w.Status),
|
||||
new ColumnDefinition<TorrentTracker>("Peers", w => w.Peers),
|
||||
new ColumnDefinition<TorrentTracker>("Seeds", w => w.Seeds),
|
||||
new ColumnDefinition<TorrentTracker>("Leeches", w => w.Leeches),
|
||||
new ColumnDefinition<TorrentTracker>("Times Downloaded", w => w.Downloads),
|
||||
new ColumnDefinition<TorrentTracker>("Message", w => w.Message),
|
||||
];
|
||||
|
||||
protected virtual async Task DisposeAsync(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_timerCancellationToken.Cancel();
|
||||
_timerCancellationToken.Dispose();
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
await DisposeAsync(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
28
Lantean.QBTMud/Components/UI/ContextMenu.razor
Normal file
28
Lantean.QBTMud/Components/UI/ContextMenu.razor
Normal file
@@ -0,0 +1,28 @@
|
||||
@inherits MudComponentBase
|
||||
|
||||
<MudMenu @ref="FakeMenu" Style="display: none" OpenChanged="FakeOpenChanged"></MudMenu>
|
||||
|
||||
@* The portal has to include the cascading values inside, because it's not able to teletransport the cascade *@
|
||||
<MudPopover tracker="@Id"
|
||||
Open="@_open"
|
||||
Class="unselectable"
|
||||
MaxHeight="@MaxHeight"
|
||||
AnchorOrigin="@AnchorOrigin"
|
||||
TransformOrigin="TransformOrigin"
|
||||
RelativeWidth="@FullWidth"
|
||||
OverflowBehavior="OverflowBehavior.FlipAlways"
|
||||
Style="@_popoverStyle"
|
||||
@ontouchend:preventDefault>
|
||||
<CascadingValue Value="@(FakeMenu)">
|
||||
@if (_showChildren)
|
||||
{
|
||||
<MudList T="object"
|
||||
Class="unselectable"
|
||||
Dense="@Dense">
|
||||
@ChildContent
|
||||
</MudList>
|
||||
}
|
||||
</CascadingValue>
|
||||
</MudPopover>
|
||||
|
||||
<MudOverlay Visible="@(_open)" LockScroll="@LockScroll" AutoClose="true" OnClosed="@CloseMenuAsync" />
|
||||
295
Lantean.QBTMud/Components/UI/ContextMenu.razor.cs
Normal file
295
Lantean.QBTMud/Components/UI/ContextMenu.razor.cs
Normal file
@@ -0,0 +1,295 @@
|
||||
using Lantean.QBTMud.EventHandlers;
|
||||
using Lantean.QBTMud.Interop;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using Microsoft.JSInterop;
|
||||
using MudBlazor;
|
||||
using MudBlazor.Utilities;
|
||||
|
||||
namespace Lantean.QBTMud.Components.UI
|
||||
{
|
||||
// This is a very hacky approach but works for now.
|
||||
// This needs to inherit from MudMenu because MudMenuItem needs a MudMenu passed to it to control the close of the menu when an item is clicked.
|
||||
// MudPopover isn't ideal for this because that is designed to be used relative to an activator which in these cases it isn't.
|
||||
// Ideally this should be changed to use something like the way the DialogService works.
|
||||
|
||||
// Or - rework this to have a hidden MudMenu and hook into the OpenChanged event to monitor when the MudMenuItem closes it.
|
||||
public partial class ContextMenu : MudComponentBase
|
||||
{
|
||||
private bool _open;
|
||||
private bool _showChildren;
|
||||
private string? _popoverStyle;
|
||||
private string? _id;
|
||||
|
||||
private double _x;
|
||||
private double _y;
|
||||
private bool _isResized = false;
|
||||
|
||||
private const double _diff = 64;
|
||||
|
||||
private string Id
|
||||
{
|
||||
get
|
||||
{
|
||||
_id ??= Guid.NewGuid().ToString();
|
||||
|
||||
return _id;
|
||||
}
|
||||
}
|
||||
|
||||
[Inject]
|
||||
public IJSRuntime JSRuntime { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
public IPopoverService PopoverService { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// If true, compact vertical padding will be applied to all menu items.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
[Category(CategoryTypes.Menu.PopupAppearance)]
|
||||
public bool Dense { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if you want to prevent page from scrolling when the menu is open
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
[Category(CategoryTypes.Menu.PopupAppearance)]
|
||||
public bool LockScroll { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, the list menu will be same width as the parent.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
[Category(CategoryTypes.Menu.PopupAppearance)]
|
||||
public bool FullWidth { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the max height the menu can have when open.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
[Category(CategoryTypes.Menu.PopupAppearance)]
|
||||
public int? MaxHeight { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set the anchor origin point to determine where the popover will open from.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
[Category(CategoryTypes.Menu.PopupAppearance)]
|
||||
public Origin AnchorOrigin { get; set; } = Origin.TopLeft;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the transform origin point for the popover.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
[Category(CategoryTypes.Menu.PopupAppearance)]
|
||||
public Origin TransformOrigin { get; set; } = Origin.TopLeft;
|
||||
|
||||
/// <summary>
|
||||
/// If true, menu will be disabled.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
[Category(CategoryTypes.Menu.Behavior)]
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether to show a ripple effect when the user clicks the button. Default is true.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
[Category(CategoryTypes.Menu.Appearance)]
|
||||
public bool Ripple { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the component has a drop-shadow. Default is true
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
[Category(CategoryTypes.Menu.Appearance)]
|
||||
public bool DropShadow { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Add menu items here
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
[Category(CategoryTypes.Menu.PopupBehavior)]
|
||||
public RenderFragment? ChildContent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Fired when the menu <see cref="Open"/> property changes.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
[Category(CategoryTypes.Menu.PopupBehavior)]
|
||||
public EventCallback<bool> OpenChanged { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public int AdjustmentX { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public int AdjustmentY { get; set; }
|
||||
|
||||
protected MudMenu? FakeMenu { get; set; }
|
||||
|
||||
protected void FakeOpenChanged(bool value)
|
||||
{
|
||||
if (!value)
|
||||
{
|
||||
_open = false;
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the menu.
|
||||
/// </summary>
|
||||
/// <param name="args">
|
||||
/// The arguments of the calling mouse/pointer event.
|
||||
/// </param>
|
||||
public async Task OpenMenuAsync(EventArgs args)
|
||||
{
|
||||
if (Disabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// long press on iOS triggers selection, so clear it
|
||||
await JSRuntime.ClearSelection();
|
||||
|
||||
if (args is not LongPressEventArgs)
|
||||
{
|
||||
_showChildren = true;
|
||||
}
|
||||
|
||||
_open = true;
|
||||
_isResized = false;
|
||||
StateHasChanged();
|
||||
|
||||
var (x, y) = GetPositionFromArgs(args);
|
||||
_x = x;
|
||||
_y = y;
|
||||
|
||||
SetPopoverStyle(x, y);
|
||||
|
||||
StateHasChanged();
|
||||
|
||||
await OpenChanged.InvokeAsync(_open);
|
||||
|
||||
// long press on iOS triggers selection, so clear it
|
||||
await JSRuntime.ClearSelection();
|
||||
|
||||
if (args is LongPressEventArgs)
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
_showChildren = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the menu.
|
||||
/// </summary>
|
||||
public Task CloseMenuAsync()
|
||||
{
|
||||
_open = false;
|
||||
_popoverStyle = null;
|
||||
StateHasChanged();
|
||||
|
||||
return OpenChanged.InvokeAsync(_open);
|
||||
}
|
||||
|
||||
private void SetPopoverStyle(double x, double y)
|
||||
{
|
||||
_popoverStyle = $"margin-top: {y.ToPx()}; margin-left: {x.ToPx()};";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggle the visibility of the menu.
|
||||
/// </summary>
|
||||
public async Task ToggleMenuAsync(EventArgs args)
|
||||
{
|
||||
if (Disabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_open)
|
||||
{
|
||||
await CloseMenuAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await OpenMenuAsync(args);
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (!_isResized)
|
||||
{
|
||||
await DeterminePosition();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeterminePosition()
|
||||
{
|
||||
var mainContentSize = await JSRuntime.GetInnerDimensions(".mud-main-content");
|
||||
double? contextMenuHeight = null;
|
||||
double? contextMenuWidth = null;
|
||||
|
||||
var popoverHolder = PopoverService.ActivePopovers.FirstOrDefault(p => p.UserAttributes.ContainsKey("tracker") && (string?)p.UserAttributes["tracker"] == Id);
|
||||
|
||||
var popoverSize = await JSRuntime.GetBoundingClientRect($"#popovercontent-{popoverHolder?.Id}");
|
||||
if (popoverSize.Height > 0)
|
||||
{
|
||||
contextMenuHeight = popoverSize.Height;
|
||||
contextMenuWidth = popoverSize.Width;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// the bottom position of the popover will be rendered off screen
|
||||
if (_y - _diff + contextMenuHeight.Value >= mainContentSize.Height)
|
||||
{
|
||||
// adjust the top of the context menu
|
||||
var overshoot = Math.Abs(mainContentSize.Height - (_y - _diff + contextMenuHeight.Value));
|
||||
_y -= overshoot;
|
||||
|
||||
if (_y - _diff + contextMenuHeight >= mainContentSize.Height)
|
||||
{
|
||||
MaxHeight = (int)(mainContentSize.Height - _y + _diff);
|
||||
}
|
||||
}
|
||||
|
||||
if (_x + contextMenuWidth.Value > mainContentSize.Width)
|
||||
{
|
||||
var overshoot = Math.Abs(mainContentSize.Width - (_x + contextMenuWidth.Value));
|
||||
_x -= overshoot;
|
||||
}
|
||||
|
||||
SetPopoverStyle(_x, _y);
|
||||
_isResized = true;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private (double x, double y) GetPositionFromArgs(EventArgs eventArgs)
|
||||
{
|
||||
double x, y;
|
||||
if (eventArgs is MouseEventArgs mouseEventArgs)
|
||||
{
|
||||
x = mouseEventArgs.ClientX;
|
||||
y = mouseEventArgs.ClientY;
|
||||
}
|
||||
else if (eventArgs is LongPressEventArgs longPressEventArgs)
|
||||
{
|
||||
x = longPressEventArgs.ClientX;
|
||||
y = longPressEventArgs.ClientY;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException("Invalid eventArgs type.");
|
||||
}
|
||||
|
||||
return (x + AdjustmentX, y + AdjustmentY);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Lantean.QBTMud/Components/UI/CustomNavLink.razor
Normal file
11
Lantean.QBTMud/Components/UI/CustomNavLink.razor
Normal file
@@ -0,0 +1,11 @@
|
||||
<div class="@Classname">
|
||||
<div @onclick="EventUtil.AsNonRenderingEventHandler<MouseEventArgs>(OnClickHandler)" class="@LinkClassname" @onlongpress="OnLongPressInternal" @oncontextmenu="OnContextMenuInternal" @oncontextmenu:preventDefault>
|
||||
@if (!string.IsNullOrEmpty(Icon))
|
||||
{
|
||||
<MudIcon Icon="@Icon" Color="@IconColor" Class="@IconClassname" />
|
||||
}
|
||||
<div class="mud-nav-link-text">
|
||||
@ChildContent
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
90
Lantean.QBTMud/Components/UI/CustomNavLink.razor.cs
Normal file
90
Lantean.QBTMud/Components/UI/CustomNavLink.razor.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
using Lantean.QBTMud.EventHandlers;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using MudBlazor;
|
||||
using MudBlazor.Utilities;
|
||||
|
||||
namespace Lantean.QBTMud.Components.UI
|
||||
{
|
||||
public partial class CustomNavLink
|
||||
{
|
||||
[Parameter]
|
||||
public bool Active { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string? Class { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool DisableRipple { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Icon to use if set.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string? Icon { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The color of the icon. It supports the theme colors, default value uses the themes drawer icon color.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Color IconColor { get; set; } = Color.Default;
|
||||
|
||||
[Parameter]
|
||||
public string? Target { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<MouseEventArgs> OnClick { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<LongPressEventArgs> OnLongPress { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<MouseEventArgs> OnContextMenu { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment? ChildContent { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment? ContextMenu { get; set; }
|
||||
|
||||
protected string Classname =>
|
||||
new CssBuilder("mud-nav-item")
|
||||
.AddClass($"mud-ripple", !DisableRipple && !Disabled)
|
||||
.AddClass(Class)
|
||||
.Build();
|
||||
|
||||
protected string LinkClassname =>
|
||||
new CssBuilder("mud-nav-link")
|
||||
.AddClass($"mud-nav-link-disabled", Disabled)
|
||||
.AddClass("active", Active)
|
||||
.Build();
|
||||
|
||||
protected string IconClassname =>
|
||||
new CssBuilder("mud-nav-link-icon")
|
||||
.AddClass($"mud-nav-link-icon-default", IconColor == Color.Default)
|
||||
.Build();
|
||||
|
||||
protected async Task OnClickHandler(MouseEventArgs ev)
|
||||
{
|
||||
if (Disabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await OnClick.InvokeAsync(ev);
|
||||
}
|
||||
|
||||
protected Task OnLongPressInternal(LongPressEventArgs e)
|
||||
{
|
||||
return OnLongPress.InvokeAsync(e);
|
||||
}
|
||||
|
||||
protected Task OnContextMenuInternal(MouseEventArgs e)
|
||||
{
|
||||
return OnContextMenu.InvokeAsync(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
63
Lantean.QBTMud/Components/UI/DynamicTable.razor
Normal file
63
Lantean.QBTMud/Components/UI/DynamicTable.razor
Normal file
@@ -0,0 +1,63 @@
|
||||
@typeparam T
|
||||
@inherits MudComponentBase
|
||||
|
||||
<MudTable
|
||||
Items="OrderedItems"
|
||||
T="T"
|
||||
Hover="true"
|
||||
FixedHeader="true"
|
||||
HeaderClass="table-head-bordered"
|
||||
Dense="true"
|
||||
Breakpoint="Breakpoint.None"
|
||||
Bordered="true"
|
||||
Striped="Striped"
|
||||
Square="true"
|
||||
LoadingProgressColor="Color.Info"
|
||||
HorizontalScrollbar="true"
|
||||
Virtualize="true"
|
||||
AllowUnsorted="false"
|
||||
SelectOnRowClick="false"
|
||||
Loading="@(Items is null)"
|
||||
MultiSelection="MultiSelection"
|
||||
SelectedItems="SelectedItems"
|
||||
SelectedItemsChanged="SelectedItemsChangedInternal"
|
||||
OnRowClick="OnRowClickInternal"
|
||||
RowStyleFunc="RowStyleFuncInternal"
|
||||
RowClassFunc="RowClassFuncInternal"
|
||||
Class="@Class">
|
||||
<ColGroup>
|
||||
@if (MultiSelection)
|
||||
{
|
||||
<col style="width: 30px" />
|
||||
}
|
||||
@foreach (var column in GetColumns())
|
||||
{
|
||||
<col style="@(GetColumnStyle(column))" />
|
||||
}
|
||||
</ColGroup>
|
||||
<HeaderContent>
|
||||
@foreach (var column in GetColumns())
|
||||
{
|
||||
var className = column.IconOnly ? null : "overflow-cell";
|
||||
var columnHeader = column.IconOnly ? "" : column.Header;
|
||||
<MudTh Class="@className" Style="@(GetColumnStyle(column))">
|
||||
@if (column.SortSelector is not null)
|
||||
{
|
||||
<SortLabel Class="column-header" SortDirectionChanged="@(c => SetSort(column.Id, c))" SortDirection="@(column.Id == _sortColumn ? _sortDirection : SortDirection.None)">@columnHeader</SortLabel>
|
||||
}
|
||||
else
|
||||
{
|
||||
@columnHeader
|
||||
}
|
||||
</MudTh>
|
||||
}
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
@foreach (var column in GetColumns())
|
||||
{
|
||||
<TdExtended @ref="_tds[column.Id]" DataLabel="@column.Header" Class="@(GetColumnClass(column, context))" Style="@(GetColumnStyle(column))" OnLongPress="@(c => OnLongPressInternal(c, column.Id, context))" OnContextMenu="@(c => OnContextMenuInternal(c, column.Id, context))">
|
||||
@column.RowTemplate(column.GetRowContext(context))
|
||||
</TdExtended>
|
||||
}
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
366
Lantean.QBTMud/Components/UI/DynamicTable.razor.cs
Normal file
366
Lantean.QBTMud/Components/UI/DynamicTable.razor.cs
Normal file
@@ -0,0 +1,366 @@
|
||||
using Blazored.LocalStorage;
|
||||
using Lantean.QBTMud.EventHandlers;
|
||||
using Lantean.QBTMud.Helpers;
|
||||
using Lantean.QBTMud.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMud.Components.UI
|
||||
{
|
||||
public partial class DynamicTable<T> : MudComponentBase
|
||||
{
|
||||
private static readonly string _typeName = typeof(T).Name;
|
||||
private readonly string _columnSelectionStorageKey = $"DynamicTable{_typeName}.ColumnSelection";
|
||||
private readonly string _columnSortStorageKey = $"DynamicTable{_typeName}.ColumnSort";
|
||||
private readonly string _columnWidthsStorageKey = $"DynamicTable{_typeName}.ColumnWidths";
|
||||
|
||||
[Inject]
|
||||
public ILocalStorageService LocalStorage { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
public IDialogService DialogService { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
[EditorRequired]
|
||||
public IEnumerable<ColumnDefinition<T>> ColumnDefinitions { get; set; } = [];
|
||||
|
||||
[Parameter]
|
||||
[EditorRequired]
|
||||
public IEnumerable<T>? Items { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool MultiSelection { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool Striped { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool Hover { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool PreSorted { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool SelectOnRowClick { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<TableRowClickEventArgs<T>> OnRowClick { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public HashSet<T> SelectedItems { get; set; } = [];
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<HashSet<T>> SelectedItemsChanged { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<T> SelectedItemChanged { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<ColumnDefinition<T>, bool> ColumnFilter { get; set; } = t => true;
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<string> SortColumnChanged { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<SortDirection> SortDirectionChanged { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<HashSet<string>> SelectedColumnsChanged { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<TableDataContextMenuEventArgs<T>> OnTableDataContextMenu { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<TableDataLongPressEventArgs<T>> OnTableDataLongPress { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<T, int, string>? RowClassFunc { get; set; }
|
||||
|
||||
protected IEnumerable<T>? OrderedItems => GetOrderedItems();
|
||||
|
||||
protected HashSet<string> SelectedColumns { get; set; } = [];
|
||||
|
||||
private Dictionary<string, int?> _columnWidths = [];
|
||||
|
||||
private string? _sortColumn;
|
||||
|
||||
private SortDirection _sortDirection;
|
||||
|
||||
private readonly Dictionary<string, TdExtended> _tds = [];
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
HashSet<string> selectedColumns;
|
||||
var storedSelectedColumns = await LocalStorage.GetItemAsync<HashSet<string>>(_columnSelectionStorageKey);
|
||||
if (storedSelectedColumns is not null)
|
||||
{
|
||||
selectedColumns = storedSelectedColumns;
|
||||
}
|
||||
else
|
||||
{
|
||||
selectedColumns = ColumnDefinitions.Where(c => c.Enabled).Select(c => c.Id).ToHashSet();
|
||||
}
|
||||
|
||||
if (!SelectedColumns.SetEquals(selectedColumns))
|
||||
{
|
||||
SelectedColumns = selectedColumns;
|
||||
await SelectedColumnsChanged.InvokeAsync(SelectedColumns);
|
||||
}
|
||||
|
||||
string? sortColumn;
|
||||
SortDirection sortDirection;
|
||||
|
||||
var sortData = await LocalStorage.GetItemAsync<SortData>(_columnSortStorageKey);
|
||||
if (sortData is not null)
|
||||
{
|
||||
sortColumn = sortData.SortColumn;
|
||||
sortDirection = sortData.SortDirection;
|
||||
}
|
||||
else
|
||||
{
|
||||
sortColumn = ColumnDefinitions.First(c => c.Enabled).Id;
|
||||
sortDirection = SortDirection.Ascending;
|
||||
}
|
||||
|
||||
if (_sortColumn != sortColumn)
|
||||
{
|
||||
_sortColumn = sortColumn;
|
||||
await SortColumnChanged.InvokeAsync(_sortColumn);
|
||||
}
|
||||
|
||||
if (_sortDirection != sortDirection)
|
||||
{
|
||||
_sortDirection = sortDirection;
|
||||
await SortDirectionChanged.InvokeAsync(_sortDirection);
|
||||
}
|
||||
|
||||
var storedColumnsWidths = await LocalStorage.GetItemAsync<Dictionary<string, int?>>(_columnWidthsStorageKey);
|
||||
if (storedColumnsWidths is not null)
|
||||
{
|
||||
_columnWidths = storedColumnsWidths;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<T>? GetOrderedItems()
|
||||
{
|
||||
if (Items is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (PreSorted)
|
||||
{
|
||||
return Items;
|
||||
}
|
||||
|
||||
var sortSelector = ColumnDefinitions.FirstOrDefault(c => c.Id == _sortColumn)?.SortSelector;
|
||||
if (sortSelector is null)
|
||||
{
|
||||
return Items;
|
||||
}
|
||||
|
||||
return Items.OrderByDirection(_sortDirection, sortSelector);
|
||||
}
|
||||
|
||||
protected IEnumerable<ColumnDefinition<T>> GetColumns()
|
||||
{
|
||||
var filteredColumns = ColumnDefinitions.Where(c => SelectedColumns.Contains(c.Id)).Where(ColumnFilter);
|
||||
foreach (var column in filteredColumns)
|
||||
{
|
||||
if (_columnWidths.TryGetValue(column.Id, out var value))
|
||||
{
|
||||
column.Width = value;
|
||||
}
|
||||
|
||||
yield return column;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SetSort(string columnId, SortDirection sortDirection)
|
||||
{
|
||||
if (sortDirection == SortDirection.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
await LocalStorage.SetItemAsync(_columnSortStorageKey, new SortData(columnId, sortDirection));
|
||||
|
||||
if (_sortColumn != columnId)
|
||||
{
|
||||
_sortColumn = columnId;
|
||||
await SortColumnChanged.InvokeAsync(_sortColumn);
|
||||
}
|
||||
|
||||
if (_sortDirection != sortDirection)
|
||||
{
|
||||
_sortDirection = sortDirection;
|
||||
await SortDirectionChanged.InvokeAsync(_sortDirection);
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task OnRowClickInternal(TableRowClickEventArgs<T> eventArgs)
|
||||
{
|
||||
if (eventArgs.Item is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (MultiSelection)
|
||||
{
|
||||
if (eventArgs.MouseEventArgs.CtrlKey)
|
||||
{
|
||||
if (SelectedItems.Contains(eventArgs.Item))
|
||||
{
|
||||
SelectedItems.Remove(eventArgs.Item);
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedItems.Add(eventArgs.Item);
|
||||
}
|
||||
}
|
||||
else if (eventArgs.MouseEventArgs.AltKey)
|
||||
{
|
||||
SelectedItems.Clear();
|
||||
SelectedItems.Add(eventArgs.Item);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!SelectedItems.Contains(eventArgs.Item))
|
||||
{
|
||||
SelectedItems.Clear();
|
||||
SelectedItems.Add(eventArgs.Item);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (SelectOnRowClick && !SelectedItems.Contains(eventArgs.Item))
|
||||
{
|
||||
SelectedItems.Clear();
|
||||
SelectedItems.Add(eventArgs.Item);
|
||||
await SelectedItemChanged.InvokeAsync(eventArgs.Item);
|
||||
}
|
||||
|
||||
await OnRowClick.InvokeAsync(eventArgs);
|
||||
}
|
||||
|
||||
protected string RowStyleFuncInternal(T item, int index)
|
||||
{
|
||||
var style = "user-select: none; cursor: pointer;";
|
||||
if (SelectOnRowClick && SelectedItems.Contains(item))
|
||||
{
|
||||
style += " background-color: var(--mud-palette-gray-dark); color: var(--mud-palette-gray-light) !important;";
|
||||
}
|
||||
return style;
|
||||
}
|
||||
|
||||
protected string RowClassFuncInternal(T item, int index)
|
||||
{
|
||||
if (RowClassFunc is not null)
|
||||
{
|
||||
return RowClassFunc(item, index);
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
protected async Task SelectedItemsChangedInternal(HashSet<T> selectedItems)
|
||||
{
|
||||
await SelectedItemsChanged.InvokeAsync(selectedItems);
|
||||
SelectedItems = selectedItems;
|
||||
}
|
||||
|
||||
protected Task OnContextMenuInternal(MouseEventArgs eventArgs, string columnId, T item)
|
||||
{
|
||||
var data = _tds[columnId];
|
||||
return OnTableDataContextMenu.InvokeAsync(new TableDataContextMenuEventArgs<T>(eventArgs, data, item));
|
||||
}
|
||||
|
||||
protected Task OnLongPressInternal(LongPressEventArgs eventArgs, string columnId, T item)
|
||||
{
|
||||
var data = _tds[columnId];
|
||||
return OnTableDataLongPress.InvokeAsync(new TableDataLongPressEventArgs<T>(eventArgs, data, item));
|
||||
}
|
||||
|
||||
public async Task ShowColumnOptionsDialog()
|
||||
{
|
||||
var result = await DialogService.ShowColumnsOptionsDialog(ColumnDefinitions.Where(ColumnFilter).ToList(), SelectedColumns, _columnWidths);
|
||||
|
||||
if (result == default)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SelectedColumns.SetEquals(result.SelectedColumns))
|
||||
{
|
||||
SelectedColumns = result.SelectedColumns;
|
||||
await LocalStorage.SetItemAsync(_columnSelectionStorageKey, SelectedColumns);
|
||||
await SelectedColumnsChanged.InvokeAsync(SelectedColumns);
|
||||
}
|
||||
|
||||
if (!DictionaryEqual(_columnWidths, result.ColumnWidths))
|
||||
{
|
||||
_columnWidths = result.ColumnWidths;
|
||||
await LocalStorage.SetItemAsync(_columnWidthsStorageKey, _columnWidths);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool DictionaryEqual(Dictionary<string, int?> left, Dictionary<string, int?> right)
|
||||
{
|
||||
return left.Keys.Count == right.Keys.Count && left.Keys.All(k => right.ContainsKey(k) && left[k] == right[k]);
|
||||
}
|
||||
|
||||
private static string? GetColumnStyle(ColumnDefinition<T> column)
|
||||
{
|
||||
string? style = null;
|
||||
if (column.Width.HasValue)
|
||||
{
|
||||
style = $"width: {column.Width.Value}px; max-width: {column.Width.Value}px;";
|
||||
}
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
private string? GetColumnClass(ColumnDefinition<T> column, T data)
|
||||
{
|
||||
var className = column.Class;
|
||||
if (column.ClassFunc is not null)
|
||||
{
|
||||
var funcClass = column.ClassFunc(data);
|
||||
if (funcClass is not null)
|
||||
{
|
||||
if (className is null)
|
||||
{
|
||||
className = funcClass;
|
||||
}
|
||||
else
|
||||
{
|
||||
className = $"{className} {funcClass}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (column.Width.HasValue)
|
||||
{
|
||||
className = $"overflow-cell {className}";
|
||||
}
|
||||
|
||||
if (OnTableDataContextMenu.HasDelegate)
|
||||
{
|
||||
className = $"no-default-context-menu {className}";
|
||||
}
|
||||
|
||||
return className;
|
||||
}
|
||||
|
||||
private sealed record SortData
|
||||
{
|
||||
public SortData(string sortColumn, SortDirection sortDirection)
|
||||
{
|
||||
SortColumn = sortColumn;
|
||||
SortDirection = sortDirection;
|
||||
}
|
||||
|
||||
public string SortColumn { get; init; }
|
||||
|
||||
public SortDirection SortDirection { get; init; }
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Lantean.QBTMud/Components/UI/FieldSwitch.razor
Normal file
3
Lantean.QBTMud/Components/UI/FieldSwitch.razor
Normal file
@@ -0,0 +1,3 @@
|
||||
<MudField Variant="Variant.Outlined" InnerPadding="false" Label="@Label" HelperText="@HelperText" Disabled="Disabled">
|
||||
<TickSwitch T="bool" Value="@Value" ValueChanged="ValueChangedCallback" Class="pt-1 pb-1" Disabled="Disabled" Validation="Validation" />
|
||||
</MudField>
|
||||
37
Lantean.QBTMud/Components/UI/FieldSwitch.razor.cs
Normal file
37
Lantean.QBTMud/Components/UI/FieldSwitch.razor.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Lantean.QBTMud.Components.UI
|
||||
{
|
||||
public partial class FieldSwitch
|
||||
{
|
||||
/// <inheritdoc cref="MudBlazor.MudBooleanInput{T}.Value"/>
|
||||
[Parameter]
|
||||
public bool Value { get; set; }
|
||||
|
||||
/// <inheritdoc cref="MudBlazor.MudBooleanInput{T}.ValueChanged"/>
|
||||
[Parameter]
|
||||
public EventCallback<bool> ValueChanged { get; set; }
|
||||
|
||||
/// <inheritdoc cref="MudBlazor.MudField.Label"/>
|
||||
[Parameter]
|
||||
public string? Label { get; set; }
|
||||
|
||||
/// <inheritdoc cref="MudBlazor.MudBooleanInput{T}.Disabled"/>
|
||||
[Parameter]
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
/// <inheritdoc cref="MudBlazor.MudFormComponent{T}.Validation"/>
|
||||
[Parameter]
|
||||
public object? Validation { get; set; }
|
||||
|
||||
/// <inheritdoc cref="MudBlazor.MudField.HelperText"/>
|
||||
[Parameter]
|
||||
public string? HelperText { get; set; }
|
||||
|
||||
protected async Task ValueChangedCallback(bool value)
|
||||
{
|
||||
Value = value;
|
||||
await ValueChanged.InvokeAsync(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
1
Lantean.QBTMud/Components/UI/NonRendering.razor
Normal file
1
Lantean.QBTMud/Components/UI/NonRendering.razor
Normal file
@@ -0,0 +1 @@
|
||||
@ChildContent
|
||||
16
Lantean.QBTMud/Components/UI/NonRendering.razor.cs
Normal file
16
Lantean.QBTMud/Components/UI/NonRendering.razor.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Lantean.QBTMud.Components.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// A simple razor wrapper that only renders the child content without any additonal html markup
|
||||
/// </summary>
|
||||
public partial class NonRendering
|
||||
{
|
||||
/// <summary>
|
||||
/// The child content to be rendered
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderFragment? ChildContent { get; set; }
|
||||
}
|
||||
}
|
||||
23
Lantean.QBTMud/Components/UI/SortLabel.razor
Normal file
23
Lantean.QBTMud/Components/UI/SortLabel.razor
Normal file
@@ -0,0 +1,23 @@
|
||||
@inherits MudComponentBase
|
||||
|
||||
<span @onclick="ToggleSortDirection" class="@Classname" style="@Style" @attributes="@UserAttributes">
|
||||
@if (!AppendIcon)
|
||||
{
|
||||
@ChildContent
|
||||
}
|
||||
@if (Enabled)
|
||||
{
|
||||
@if (SortDirection != SortDirection.None)
|
||||
{
|
||||
<MudIcon Icon="@SortIcon" Class="@GetSortIconClass()" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudIcon Icon="@SortIcon" Class="mud-table-sort-label-icon" />
|
||||
}
|
||||
}
|
||||
@if (AppendIcon)
|
||||
{
|
||||
@ChildContent
|
||||
}
|
||||
</span>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user