mirror of
https://github.com/lantean-code/qbtmud.git
synced 2025-11-02 13:03:23 +00:00
Add new torrent filters
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
# Upgrade to qBittorrent WebUI v5 – UI Alignment Plan
|
||||
|
||||
## Torrent List Filtering
|
||||
- **Regex toggle & field selector**: Introduce the regex checkbox and the "Filter by" (Name/Save path) select found in v5. Update `FilterState`/`LoggedInLayout` to carry both values, wire them to `TorrentList`’s toolbar, and validate invalid patterns gracefully.
|
||||
- **Filter helper parity**: Rework `FilterHelper.ContainsAllTerms/FilterTerms` to mirror `window.qBittorrent.Misc.containsAllTerms` (evaluate every term, respect `+`/`-` prefixes). Ensure filtering applies to the selected field, not just the torrent name.
|
||||
- **New status buckets**: Add `Running` and `Moving` to `Status` enum, update `FilterHelper.FilterStatus`, `DisplayHelpers`, and `FiltersNav` so counts/icons match upstream.
|
||||
## ~~Torrent List Filtering~~
|
||||
- ~~**Regex toggle & field selector**: Introduce the regex checkbox and the "Filter by" (Name/Save path) select found in v5. Update `FilterState`/`LoggedInLayout` to carry both values, wire them to `TorrentList`’s toolbar, and validate invalid patterns gracefully.~~
|
||||
- ~~**Filter helper parity**: Rework `FilterHelper.ContainsAllTerms/FilterTerms` to mirror `window.qBittorrent.Misc.containsAllTerms` (evaluate every term, respect `+`/`-` prefixes). Ensure filtering applies to the selected field, not just the torrent name.~~
|
||||
- ~~**New status buckets**: Add `Running` and `Moving` to `Status` enum, update `FilterHelper.FilterStatus`, `DisplayHelpers`, and `FiltersNav` so counts/icons match upstream.~~
|
||||
|
||||
## ~~Tracker Filters~~
|
||||
- ~~**Special buckets**: Extend `FilterHelper`/`DataManager` to create sets for "Announce error", "Error", "Warning", and "Trackerless" in addition to "All". Store the required flags on the UI `Torrent` model (`HasTrackerError`, `HasTrackerWarning`, `HasOtherAnnounceError`, `TrackersCount`, etc.).~~
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
## ~~Torrent Data Model & Columns~~
|
||||
- ~~**Model sync**: Bring `Lantean.QBTMud.Models.Torrent` into parity with v5 (`Popularity`, `DownloadPath`, `RootPath`, `InfoHashV1/2`, `IsPrivate`, share-limit action fields, tracker flags, etc.) and map them in `DataManager.CreateTorrent`.~~
|
||||
- ~~**Column set alignment**: Match the v5 table defaults—add missing columns (Popularity, Reannounce in, Info hashes, Download path, Private, etc.), fix "Ratio Limit" to display `RatioLimit`, and ensure column ordering/enabled state mirrors `DynamicTable.TorrentsTable`.~~
|
||||
- ~~**Column set alignment**: Match the v5 table defaults—add missing columns (`Popularity`, `Reannounce` in, `Info` hashes, `Download path`, `Private`, etc.), fix "Ratio Limit" to display `RatioLimit`, and ensure column ordering/enabled state mirrors `DynamicTable.TorrentsTable`.~~
|
||||
- ~~**Helper updates**: Extend `DisplayHelpers` to format the new fields (popularity, private flag, info hashes, error state icons).~~
|
||||
|
||||
## Actions & Dialogs
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<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>
|
||||
<MudTextField @ref="SearchInput" T="string" Value="SearchText" ValueChanged="SearchTextChanged" Immediate="true" DebounceInterval="500" Placeholder="Filter file list" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="mt-0"></MudTextField>
|
||||
</MudToolBar>
|
||||
</div>
|
||||
<div class="content-panel__body">
|
||||
@@ -93,4 +93,4 @@
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,8 @@ namespace Lantean.QBTMud.Components
|
||||
|
||||
private MudMenu? ContextMenu { get; set; }
|
||||
|
||||
private MudTextField<string>? SearchInput { get; set; }
|
||||
|
||||
public FilesTab()
|
||||
{
|
||||
_columnRenderFragments.Add("Name", NameColumn);
|
||||
@@ -235,7 +237,11 @@ namespace Lantean.QBTMud.Components
|
||||
{
|
||||
MarkFilesDirty();
|
||||
PruneSelectionIfMissing();
|
||||
await InvokeAsync(StateHasChanged);
|
||||
await InvokeAsync(() =>
|
||||
{
|
||||
SyncSearchTextFromInput();
|
||||
StateHasChanged();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -415,6 +421,11 @@ namespace Lantean.QBTMud.Components
|
||||
|
||||
private ReadOnlyCollection<ContentItem> GetFiles()
|
||||
{
|
||||
if (SyncSearchTextFromInput())
|
||||
{
|
||||
_filesDirty = true;
|
||||
}
|
||||
|
||||
if (!_filesDirty)
|
||||
{
|
||||
return _visibleFiles;
|
||||
@@ -522,6 +533,23 @@ namespace Lantean.QBTMud.Components
|
||||
return visible;
|
||||
}
|
||||
|
||||
private bool SyncSearchTextFromInput()
|
||||
{
|
||||
if (SearchInput is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var currentValue = SearchInput.Value;
|
||||
if (string.Equals(SearchText, currentValue, StringComparison.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
SearchText = currentValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void MarkFilesDirty()
|
||||
{
|
||||
_filesDirty = true;
|
||||
@@ -629,4 +657,4 @@ namespace Lantean.QBTMud.Components
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<ContentItem>("Availability", c => c.Availability, c => c.Availability.ToString("0.00")),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using Lantean.QBTMud.Models;
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using Lantean.QBTMud.Models;
|
||||
|
||||
namespace Lantean.QBTMud.Helpers
|
||||
{
|
||||
@@ -20,7 +22,7 @@ namespace Lantean.QBTMud.Helpers
|
||||
.Where(t => FilterTag(t, filterState.Tag))
|
||||
.Where(t => FilterCategory(t, filterState.Category, filterState.UseSubcategories))
|
||||
.Where(t => FilterTracker(t, filterState.Tracker))
|
||||
.Where(t => FilterTerms(t.Name, filterState.Terms));
|
||||
.Where(t => FilterTerms(t, filterState));
|
||||
}
|
||||
|
||||
public static HashSet<string> ToHashesHashSet(this IEnumerable<Torrent> torrents)
|
||||
@@ -60,46 +62,111 @@ namespace Lantean.QBTMud.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
public static bool ContainsAllTerms(string text, IEnumerable<string> terms)
|
||||
public static bool ContainsAllTerms(string text, IEnumerable<string> terms, bool useRegex)
|
||||
{
|
||||
return terms.Any(t =>
|
||||
{
|
||||
var term = t;
|
||||
var isTermRequired = term[0] == '+';
|
||||
var isTermExcluded = term[0] == '-';
|
||||
var target = text ?? string.Empty;
|
||||
|
||||
if (isTermRequired || isTermExcluded)
|
||||
foreach (var rawTerm in terms)
|
||||
{
|
||||
if (string.IsNullOrEmpty(rawTerm))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var term = rawTerm;
|
||||
var isExclude = false;
|
||||
if (term[0] == '+' || term[0] == '-')
|
||||
{
|
||||
isExclude = term[0] == '-';
|
||||
if (term.Length == 1)
|
||||
{
|
||||
return true;
|
||||
continue;
|
||||
}
|
||||
|
||||
term = term[1..];
|
||||
}
|
||||
|
||||
var textContainsTerm = text.Contains(term, StringComparison.OrdinalIgnoreCase);
|
||||
return isTermExcluded ? !textContainsTerm : textContainsTerm;
|
||||
});
|
||||
if (string.IsNullOrEmpty(term))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isExclude)
|
||||
{
|
||||
if (MatchesTerm(target, term, useRegex))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!MatchesTerm(target, term, useRegex))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool FilterTerms(string field, string? terms)
|
||||
{
|
||||
if (terms is null || terms == "")
|
||||
return FilterTerms(field, terms, useRegex: false, isRegexValid: true);
|
||||
}
|
||||
|
||||
public static bool FilterTerms(string field, string? terms, bool useRegex, bool isRegexValid)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(terms))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return ContainsAllTerms(field, terms.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries));
|
||||
if (useRegex && !isRegexValid)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var value = field ?? string.Empty;
|
||||
var tokens = terms.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
|
||||
return ContainsAllTerms(value, tokens, useRegex);
|
||||
}
|
||||
|
||||
public static bool FilterTerms(Torrent torrent, FilterState filterState)
|
||||
{
|
||||
return FilterTerms(GetFilterFieldValue(torrent, filterState.FilterField), filterState.Terms, filterState.UseRegex, filterState.IsRegexValid);
|
||||
}
|
||||
|
||||
public static bool FilterTerms(Torrent torrent, string? terms)
|
||||
{
|
||||
if (terms is null || terms == "")
|
||||
return FilterTerms(torrent.Name, terms, useRegex: false, isRegexValid: true);
|
||||
}
|
||||
|
||||
private static string GetFilterFieldValue(Torrent torrent, TorrentFilterField field)
|
||||
{
|
||||
return field switch
|
||||
{
|
||||
return true;
|
||||
TorrentFilterField.SavePath => torrent.SavePath ?? string.Empty,
|
||||
_ => torrent.Name ?? string.Empty,
|
||||
};
|
||||
}
|
||||
|
||||
private static bool MatchesTerm(string text, string term, bool useRegex)
|
||||
{
|
||||
if (!useRegex)
|
||||
{
|
||||
return text.Contains(term, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
return ContainsAllTerms(torrent.Name, terms.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries));
|
||||
try
|
||||
{
|
||||
return Regex.IsMatch(text, term, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool FilterTracker(Torrent torrent, string tracker)
|
||||
|
||||
@@ -21,6 +21,6 @@ namespace Lantean.QBTMud.Layout
|
||||
public EventCallback<string> TrackerChanged { get; set; }
|
||||
|
||||
[CascadingParameter(Name = "SearchTermChanged")]
|
||||
public EventCallback<string> SearchTermChanged { get; set; }
|
||||
public EventCallback<FilterSearchState> SearchTermChanged { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,12 @@ namespace Lantean.QBTMud.Layout
|
||||
|
||||
protected string? SearchText { get; set; }
|
||||
|
||||
protected TorrentFilterField SearchField { get; set; } = TorrentFilterField.Name;
|
||||
|
||||
protected bool UseRegexSearch { get; set; }
|
||||
|
||||
protected bool IsRegexValid { get; set; } = true;
|
||||
|
||||
protected IReadOnlyList<Torrent> Torrents => GetTorrents();
|
||||
|
||||
protected bool IsAuthenticated { get; set; }
|
||||
@@ -77,7 +83,16 @@ namespace Lantean.QBTMud.Layout
|
||||
return _visibleTorrents;
|
||||
}
|
||||
|
||||
var filterState = new FilterState(Category, Status, Tag, Tracker, MainData.ServerState.UseSubcategories, SearchText);
|
||||
var filterState = new FilterState(
|
||||
Category,
|
||||
Status,
|
||||
Tag,
|
||||
Tracker,
|
||||
MainData.ServerState.UseSubcategories,
|
||||
SearchText,
|
||||
SearchField,
|
||||
UseRegexSearch,
|
||||
IsRegexValid);
|
||||
_visibleTorrents = MainData.Torrents.Values.Filter(filterState).ToList();
|
||||
_torrentsDirty = false;
|
||||
|
||||
@@ -185,7 +200,7 @@ namespace Lantean.QBTMud.Layout
|
||||
|
||||
protected EventCallback<string> TrackerChanged => EventCallback.Factory.Create<string>(this, OnTrackerChanged);
|
||||
|
||||
protected EventCallback<string> SearchTermChanged => EventCallback.Factory.Create<string>(this, OnSearchTermChanged);
|
||||
protected EventCallback<FilterSearchState> SearchTermChanged => EventCallback.Factory.Create<FilterSearchState>(this, OnSearchTermChanged);
|
||||
|
||||
protected EventCallback<string> SortColumnChanged => EventCallback.Factory.Create<string>(this, columnId => SortColumn = columnId);
|
||||
|
||||
@@ -271,14 +286,23 @@ namespace Lantean.QBTMud.Layout
|
||||
MarkTorrentsDirty();
|
||||
}
|
||||
|
||||
private void OnSearchTermChanged(string term)
|
||||
private void OnSearchTermChanged(FilterSearchState state)
|
||||
{
|
||||
if (SearchText == term)
|
||||
var hasChanges =
|
||||
SearchText != state.Text ||
|
||||
SearchField != state.Field ||
|
||||
UseRegexSearch != state.UseRegex ||
|
||||
IsRegexValid != state.IsRegexValid;
|
||||
|
||||
if (!hasChanges)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SearchText = term;
|
||||
SearchText = state.Text;
|
||||
SearchField = state.Field;
|
||||
UseRegexSearch = state.UseRegex;
|
||||
IsRegexValid = state.IsRegexValid;
|
||||
MarkTorrentsDirty();
|
||||
}
|
||||
|
||||
|
||||
21
src/Lantean.QBTMud/Models/FilterSearchState.cs
Normal file
21
src/Lantean.QBTMud/Models/FilterSearchState.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
namespace Lantean.QBTMud.Models
|
||||
{
|
||||
public readonly struct FilterSearchState
|
||||
{
|
||||
public FilterSearchState(string? text, TorrentFilterField field, bool useRegex, bool isRegexValid)
|
||||
{
|
||||
Text = text;
|
||||
Field = field;
|
||||
UseRegex = useRegex;
|
||||
IsRegexValid = isRegexValid;
|
||||
}
|
||||
|
||||
public string? Text { get; }
|
||||
|
||||
public TorrentFilterField Field { get; }
|
||||
|
||||
public bool UseRegex { get; }
|
||||
|
||||
public bool IsRegexValid { get; }
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,16 @@
|
||||
{
|
||||
public readonly struct FilterState
|
||||
{
|
||||
public FilterState(string category, Status status, string tag, string tracker, bool useSubcategories, string? terms)
|
||||
public FilterState(
|
||||
string category,
|
||||
Status status,
|
||||
string tag,
|
||||
string tracker,
|
||||
bool useSubcategories,
|
||||
string? terms,
|
||||
TorrentFilterField filterField,
|
||||
bool useRegex,
|
||||
bool isRegexValid)
|
||||
{
|
||||
Category = category;
|
||||
Status = status;
|
||||
@@ -10,6 +19,9 @@
|
||||
Tracker = tracker;
|
||||
UseSubcategories = useSubcategories;
|
||||
Terms = terms;
|
||||
FilterField = filterField;
|
||||
UseRegex = useRegex;
|
||||
IsRegexValid = isRegexValid;
|
||||
}
|
||||
|
||||
public string Category { get; } = "all";
|
||||
@@ -18,5 +30,8 @@
|
||||
public string Tracker { get; } = "all";
|
||||
public bool UseSubcategories { get; }
|
||||
public string? Terms { get; }
|
||||
public TorrentFilterField FilterField { get; } = TorrentFilterField.Name;
|
||||
public bool UseRegex { get; }
|
||||
public bool IsRegexValid { get; } = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
8
src/Lantean.QBTMud/Models/TorrentFilterField.cs
Normal file
8
src/Lantean.QBTMud/Models/TorrentFilterField.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Lantean.QBTMud.Models
|
||||
{
|
||||
public enum TorrentFilterField
|
||||
{
|
||||
Name = 0,
|
||||
SavePath = 1
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
@page "/"
|
||||
@layout ListLayout
|
||||
@using Lantean.QBTMud.Models
|
||||
|
||||
<MudMenu @ref="ContextMenu" Dense="true" RelativeWidth="DropdownWidth.Ignore" PositionAtCursor="true" ListClass="unselectable" PopoverClass="unselectable">
|
||||
<MudMenuItem Icon="@Icons.Material.Outlined.Info" IconColor="Color.Inherit" OnClick="ShowTorrentContextMenu">View torrent details</MudMenuItem>
|
||||
@@ -18,7 +19,12 @@
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.Info" Color="Color.Inherit" Disabled="@(!ToolbarButtonsEnabled)" OnClick="ShowTorrentToolbar" title="View torrent details" />
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.ViewColumn" Color="Color.Inherit" OnClick="ColumnOptions" title="Choose Columns" />
|
||||
<MudSpacer />
|
||||
<MudTextField Value="SearchText" TextChanged="SearchTextChanged" Immediate="true" DebounceInterval="1000" Placeholder="Filter torrent list" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="mt-0"></MudTextField>
|
||||
<MudSelect T="TorrentFilterField" @bind-Value="SearchField" @bind-Value:after="OnSearchFieldChanged" Dense="true" Class="mt-0 mr-2 filter-field-select" AnchorOrigin="Origin.BottomRight" TransformOrigin="Origin.TopRight" Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="TorrentFilterField.Name">Name</MudSelectItem>
|
||||
<MudSelectItem Value="TorrentFilterField.SavePath">Save path</MudSelectItem>
|
||||
</MudSelect>
|
||||
<MudCheckBox T="bool" @bind-Value="UseRegex" @bind-Value:after="OnUseRegexChanged" Label="Regex" Class="mt-0 mr-2" />
|
||||
<MudTextField Value="SearchText" TextChanged="SearchTextChanged" Immediate="true" DebounceInterval="1000" Placeholder="Filter torrent list" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="mt-0" Error="@(UseRegex && !IsRegexValid)" ErrorText="@SearchErrorText"></MudTextField>
|
||||
</MudToolBar>
|
||||
</div>
|
||||
<div class="content-panel__body">
|
||||
@@ -69,4 +75,4 @@
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using Lantean.QBTMud.Services;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.JSInterop;
|
||||
using MudBlazor;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Lantean.QBTMud.Pages
|
||||
{
|
||||
@@ -47,7 +48,7 @@ namespace Lantean.QBTMud.Pages
|
||||
public int TorrentsVersion { get; set; }
|
||||
|
||||
[CascadingParameter(Name = "SearchTermChanged")]
|
||||
public EventCallback<string> SearchTermChanged { get; set; }
|
||||
public EventCallback<FilterSearchState> SearchTermChanged { get; set; }
|
||||
|
||||
[CascadingParameter(Name = "SortColumnChanged")]
|
||||
public EventCallback<string> SortColumnChanged { get; set; }
|
||||
@@ -60,6 +61,14 @@ namespace Lantean.QBTMud.Pages
|
||||
|
||||
protected string? SearchText { get; set; }
|
||||
|
||||
protected TorrentFilterField SearchField { get; set; } = TorrentFilterField.Name;
|
||||
|
||||
protected bool UseRegex { get; set; }
|
||||
|
||||
protected bool IsRegexValid { get; set; } = true;
|
||||
|
||||
protected string? SearchErrorText { get; set; }
|
||||
|
||||
protected HashSet<Torrent> SelectedItems { get; set; } = [];
|
||||
|
||||
protected bool ToolbarButtonsEnabled => _toolbarButtonsEnabled;
|
||||
@@ -179,7 +188,19 @@ namespace Lantean.QBTMud.Pages
|
||||
protected async Task SearchTextChanged(string text)
|
||||
{
|
||||
SearchText = text;
|
||||
await SearchTermChanged.InvokeAsync(SearchText);
|
||||
ValidateRegex();
|
||||
await PublishSearchStateAsync();
|
||||
}
|
||||
|
||||
protected async Task OnSearchFieldChanged()
|
||||
{
|
||||
await PublishSearchStateAsync();
|
||||
}
|
||||
|
||||
protected async Task OnUseRegexChanged()
|
||||
{
|
||||
ValidateRegex();
|
||||
await PublishSearchStateAsync();
|
||||
}
|
||||
|
||||
protected async Task AddTorrentFile()
|
||||
@@ -220,6 +241,41 @@ namespace Lantean.QBTMud.Pages
|
||||
return [(ContextMenuItem is null ? "fake" : ContextMenuItem.Hash)];
|
||||
}
|
||||
|
||||
private async Task PublishSearchStateAsync()
|
||||
{
|
||||
var state = new FilterSearchState(SearchText, SearchField, UseRegex, IsRegexValid);
|
||||
await SearchTermChanged.InvokeAsync(state);
|
||||
}
|
||||
|
||||
private void ValidateRegex()
|
||||
{
|
||||
if (!UseRegex)
|
||||
{
|
||||
IsRegexValid = true;
|
||||
SearchErrorText = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(SearchText))
|
||||
{
|
||||
IsRegexValid = true;
|
||||
SearchErrorText = null;
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_ = new Regex(SearchText, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||
IsRegexValid = true;
|
||||
SearchErrorText = null;
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
IsRegexValid = false;
|
||||
SearchErrorText = "Invalid regular expression";
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ColumnOptions()
|
||||
{
|
||||
if (Table is null)
|
||||
|
||||
Reference in New Issue
Block a user