ui(manager): improve search bindings

This commit is contained in:
2025-11-18 16:21:34 +01:00
parent 86f93cf3d7
commit 2cbe25e0f8
12 changed files with 89 additions and 52 deletions

View File

@@ -70,7 +70,7 @@ internal static class SharedFunctions
foreach (var update in updates)
{
var sourceTag = update.AvailableVersions[update.NewVersion].Tag;
if (api.Model.CurrentGridRows?.FirstOrDefault(n => n.Action == update.Origin) is { } row)
if (api.Model.CurrentGridRows.List.Items.FirstOrDefault(n => n.Action == update.Origin) is { } row)
row.SourceTag = sourceTag;
else
update.Origin.SourceTag = sourceTag;

View File

@@ -17,10 +17,10 @@ internal class CheckAllActionsHealthyFeature : PluginFunction, IPluginFeaturePro
protected override async Task<object?> ExecuteFunctionAsync(PluginFunctionParameter? @params)
{
if (@params is not MainApiParameters p || p.Api.Model.CurrentGridRows is null)
if (@params is not MainApiParameters p)
return null;
await SharedFunctions.CheckActionHealthy(p.Api, [.. p.Api.Model.CurrentGridRows]);
await SharedFunctions.CheckActionHealthy(p.Api, [.. p.Api.Model.CurrentGridRows.View]);
return null;
}

View File

@@ -17,8 +17,8 @@ internal class ClearDirectLinksFeature : PluginFunction, IPluginFeatureProvider<
protected override Task<object?> ExecuteFunctionAsync(PluginFunctionParameter? @params)
{
if (@params is MainApiParameters p && p.Api.Model.CurrentGridRows is not null)
SharedFunctions.ClearDirectLinks(p.Api, [.. p.Api.Model.CurrentGridRows]);
if (@params is MainApiParameters p)
SharedFunctions.ClearDirectLinks(p.Api, [.. p.Api.Model.CurrentGridRows.View]);
return Task.FromResult<object?>(null);
}
}

View File

@@ -17,10 +17,10 @@ internal class UpdateDirectLinksFeature : PluginFunction, IPluginFeatureProvider
protected override async Task<object?> ExecuteFunctionAsync(PluginFunctionParameter? @params)
{
if (@params is not MainApiParameters p || p.Api.Model.CurrentGridRows is null)
if (@params is not MainApiParameters p)
return null;
await SharedFunctions.FindNewDirectLinks(p.Api, [.. p.Api.Model.CurrentGridRows]);
await SharedFunctions.FindNewDirectLinks(p.Api, [.. p.Api.Model.CurrentGridRows.View]);
return null;
}

View File

@@ -50,6 +50,7 @@
<ItemGroup>
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.3.8" />
<PackageReference Include="DynamicData" Version="9.4.1" />
<PackageReference Include="MessageBox.Avalonia" Version="3.3.0" />
<PackageReference Include="EPPlus" Version="8.2.1" />
<PackageReference Include="NGitLab" Version="11.0.1" />

View File

@@ -120,6 +120,18 @@
<StackPanel
Orientation="Horizontal">
<!-- TextBox: Version -->
<TextBox
Margin="3, 0, 3, 0"
Width="100"
Text="{Binding Version}"/>
<!-- CheckBox: Is public -->
<CheckBox
Margin="3, 0, 3, 0"
Content="{x:Static langRes:GeneralLangRes.Public}"
IsChecked="{Binding IsPublic}"/>
<!-- Button: Add action -->
<pilz:ImageButton
x:Name="ButtonAddAction"
@@ -135,18 +147,6 @@
ImageSource="{x:Static local:MainWindow.ButtonImageRemoveAction}"
Background="Transparent"
Click="ButtonRemoveAction_OnClick"/>
<!-- TextBox: Version -->
<TextBox
Margin="3, 0, 3, 0"
Width="100"
Text="{Binding Version}"/>
<!-- CheckBox: Is public -->
<CheckBox
Margin="3, 0, 3, 0"
Content="{x:Static langRes:GeneralLangRes.Public}"
IsChecked="{Binding IsPublic}"/>
</StackPanel>
</DataTemplate>
@@ -172,7 +172,7 @@
Grid.Row="1"
x:Name="DataGridActions"
VerticalAlignment="Stretch"
ItemsSource="{Binding CurrentGridRows}"
ItemsSource="{Binding CurrentGridRows.View}"
SelectedItem="{Binding SelectedGridRow}">
<DataGrid.ContextMenu>

View File

@@ -3,6 +3,7 @@ using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Media;
using DynamicData;
using ModpackUpdater.Apps.Manager.Api;
using ModpackUpdater.Apps.Manager.Api.Model;
using ModpackUpdater.Apps.Manager.Api.Plugins.Features;
@@ -129,7 +130,7 @@ public partial class MainWindow : Window, IMainApi
private async void Window_OnLoaded(object? sender, RoutedEventArgs e)
{
var updater = new AppUpdates(Program.UpdateUrl, this);
updater.OnDownloadProgramUpdate += (o, args) => Model.Progress.Start();
updater.OnDownloadProgramUpdate += (o, _) => Model.Progress.Start();
await updater.UpdateApp();
Model.Progress.Stop();
@@ -239,7 +240,7 @@ public partial class MainWindow : Window, IMainApi
return;
}
rows.Add(new MainWindowGridRow(action, rootInfos));
rows.List.Add(new MainWindowGridRow(action, rootInfos));
}
private void ButtonRemoveAction_OnClick(object? sender, RoutedEventArgs e)
@@ -262,6 +263,6 @@ public partial class MainWindow : Window, IMainApi
return;
}
rows.Remove(row);
rows.List.Remove(row);
}
}

View File

@@ -0,0 +1,38 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using DynamicData;
namespace ModpackUpdater.Apps.Manager.Ui.Models;
public class DynamicDataView<T> : INotifyPropertyChanged where T : notnull
{
public event PropertyChangedEventHandler? PropertyChanged;
private string? searchText;
private readonly Subject<string?> searchTextSubject = new();
public SourceList<T> List { get; } = new();
public ReadOnlyObservableCollection<T> View { get; }
public DynamicDataView(Func<string?, Func<T, bool>> predicate)
{
List.Connect()
.Filter(searchTextSubject/*.Throttle(TimeSpan.FromMilliseconds(250))*/.Select(predicate))
.Bind(out var view)
.Subscribe();
searchTextSubject?.OnNext(searchText);
View = view;
}
public string? SearchText
{
get => searchText;
set
{
searchText = value;
searchTextSubject.OnNext(value);
}
}
}

View File

@@ -11,13 +11,13 @@ public class MainWindowViewModel : INotifyPropertyChanged
public event PropertyChangedEventHandler? PropertyChanged;
private ObservableCollection<MainWindowTreeNode>? currentTreeNodes;
private ObservableCollection<MainWindowGridRow>? currentGridRows;
private MainWindowTreeNode? selectedTreeNode;
private IWorkspace? currentWorkspace;
public bool IsUpdate => selectedTreeNode is ActionSetTreeNode node && node.Infos is UpdateInfo;
public ProgressInfos Progress { get; } = new();
public MainWindowGridRow? SelectedGridRow { get; set; }
public DynamicDataView<MainWindowGridRow> CurrentGridRows { get; } = new(FilterGridRows);
[AlsoNotifyFor(nameof(CurrentTreeNodes))]
public IWorkspace? CurrentWorkspace
@@ -48,24 +48,23 @@ public class MainWindowViewModel : INotifyPropertyChanged
}
}
[AlsoNotifyFor(nameof(CurrentGridRows))]
public MainWindowTreeNode? SelectedTreeNode
{
get => selectedTreeNode;
set
{
currentGridRows = null;
selectedTreeNode = value;
CurrentGridRows.List.Edit(list =>
{
list.Clear();
if (CurrentWorkspace?.InstallInfos != null && selectedTreeNode is ActionSetTreeNode node)
list.AddRange(node.Infos.Actions.Select(n => new MainWindowGridRow(n, CurrentWorkspace.InstallInfos)));
});
}
}
public ObservableCollection<MainWindowGridRow>? CurrentGridRows
private static Func<MainWindowGridRow, bool> FilterGridRows(string? searchText)
{
get
{
if (currentGridRows == null && CurrentWorkspace?.InstallInfos != null && selectedTreeNode is ActionSetTreeNode node)
currentGridRows = [.. node.Infos.Actions.Select(n => new MainWindowGridRow(n, CurrentWorkspace.InstallInfos))];
return currentGridRows;
}
return n => string.IsNullOrWhiteSpace(searchText) || true;
}
}

View File

@@ -1,5 +1,9 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Reactive;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using DynamicData;
using PropertyChanged;
namespace ModpackUpdater.Apps.Manager.Ui.Models.UpdatesCollectorViewMode;
@@ -9,6 +13,10 @@ public class UpdatesCollectorViewModel : INotifyPropertyChanged
public event PropertyChangedEventHandler? PropertyChanged;
public ProgressInfos Progress { get; } = new();
public string? SearchText { get; set; }
public ObservableCollection<ModUpdateInfo> Updates { get; } = [];
public DynamicDataView<ModUpdateInfo> Updates { get; } = new(FilterUpdates);
private static Func<ModUpdateInfo, bool> FilterUpdates(string? searchText)
{
return n => string.IsNullOrWhiteSpace(searchText) || (n.Origin.Name != null && n.Origin.Name.Contains(searchText, StringComparison.InvariantCultureIgnoreCase));
}
}

View File

@@ -26,15 +26,14 @@
<TextBox
Grid.Row="0"
Watermark="Search"
Text="{Binding SearchText}"
TextChanged="TextBoxSearch_OnTextChanged"/>
Text="{Binding Updates.SearchText}"/>
<!-- ScrollViewer: Updates -->
<ScrollViewer
Grid.Row="1">
<ItemsControl
ItemsSource="{Binding Updates}">
ItemsSource="{Binding Updates.View}">
<ItemsControl.ItemTemplate>
<DataTemplate>
@@ -43,8 +42,7 @@
ColumnDefinitions="20*,20*,20*,Auto"
ColumnSpacing="6"
RowSpacing="6"
Margin="3"
IsVisible="{Binding Visible}">
Margin="3">
<!-- Label: Name -->
<TextBlock

View File

@@ -1,5 +1,6 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using DynamicData;
using ModpackUpdater.Apps.Manager.Api.Model;
using ModpackUpdater.Apps.Manager.Ui.Models.UpdatesCollectorViewMode;
using ModpackUpdater.Manager;
@@ -36,7 +37,7 @@ public partial class UpdatesCollectorView : AvaloniaFlyoutBase
if (updates == null || updates.Length == 0 || updates[0].Tag == action.SourceTag)
continue;
Model.Updates.Add(new(updates, action));
Model.Updates.List.Add(new(updates, action));
if (IsClosed)
break;
@@ -47,7 +48,7 @@ public partial class UpdatesCollectorView : AvaloniaFlyoutBase
protected override object GetResult()
{
return new ModUpdates(Model.Updates);
return new ModUpdates([.. Model.Updates.List.Items]);
}
private async void Me_OnLoaded(object? sender, RoutedEventArgs e)
@@ -55,18 +56,9 @@ public partial class UpdatesCollectorView : AvaloniaFlyoutBase
await FindUpdates();
}
private void TextBoxSearch_OnTextChanged(object? sender, TextChangedEventArgs e)
{
var searchString = Model.SearchText?.Trim().ToLowerInvariant();
var hasNoSearch = string.IsNullOrWhiteSpace(searchString);
foreach (var item in Model.Updates)
item.Visible = hasNoSearch || (item.Origin.Name != null && item.Origin.Name.Contains(searchString!, StringComparison.InvariantCultureIgnoreCase));
}
private void ButtonRemoveUpdate_Click(object? sender, RoutedEventArgs e)
{
if (sender is Button button && button.DataContext is ModUpdateInfo update)
Model.Updates.Remove(update);
Model.Updates.List.Remove(update);
}
}