Add search functionality to plugin manager and improve UX

This commit is contained in:
Stone_Red
2023-12-27 15:45:51 +01:00
parent 8a275caba8
commit f46f57d11a
8 changed files with 198 additions and 30 deletions
@@ -1,13 +1,19 @@
using DesktopMagic.Plugins;
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Input;
namespace DesktopMagic.DataContexts;
internal class PluginEntryDataContext(PluginMetadata pluginMetadata, ICommand command, bool installed = false)
internal class PluginEntryDataContext(PluginMetadata pluginMetadata, ICommand command, bool installed = false) : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
private bool isVisible = true;
public string Name => pluginMetadata.Name;
public string? Description => pluginMetadata.Description;
@@ -24,4 +30,22 @@ internal class PluginEntryDataContext(PluginMetadata pluginMetadata, ICommand co
public Visibility InstallButtonVisibility => installed ? Visibility.Collapsed : Visibility.Visible;
public Visibility RemoveButtonVisibility => installed ? Visibility.Visible : Visibility.Collapsed;
public bool IsVisible
{
get => isVisible;
set
{
isVisible = value;
OnPropertyChanged();
OnPropertyChanged(nameof(Visibility));
}
}
public Visibility Visibility => IsVisible ? Visibility.Visible : Visibility.Collapsed;
private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
@@ -8,17 +8,19 @@ internal class PluginManagerDataContext : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
private string allPluginsSearchText = string.Empty;
private string installedPluginsSearchText = string.Empty;
private bool isLoading = true;
private bool isSearching;
public ObservableCollection<PluginEntryDataContext> AllPlugins { get; } = [];
public ObservableCollection<PluginEntryDataContext> InstalledPlugins { get; } = [];
private bool _isLoading;
public bool IsLoading
{
get => _isLoading;
get => isLoading;
set
{
_isLoading = value;
isLoading = value;
OnPropertyChanged();
OnPropertyChanged(nameof(IsNotLoading));
}
@@ -26,6 +28,36 @@ internal class PluginManagerDataContext : INotifyPropertyChanged
public bool IsNotLoading => !IsLoading;
public bool IsSearching
{
get => isSearching;
set
{
isSearching = value;
OnPropertyChanged();
}
}
public string AllPluginsSearchText
{
get => allPluginsSearchText;
set
{
allPluginsSearchText = value;
OnPropertyChanged();
}
}
public string InstalledPluginsSearchText
{
get => installedPluginsSearchText;
set
{
installedPluginsSearchText = value;
OnPropertyChanged();
}
}
protected void OnPropertyChanged([CallerMemberName] string? name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
+3 -3
View File
@@ -7,11 +7,11 @@
<ApplicationIcon>icon.ico</ApplicationIcon>
<RunPostBuildEvent>OnBuildSuccess</RunPostBuildEvent>
<Authors>Stone_Red</Authors>
<Version>0.0.4.0</Version>
<Version>1.0.0.0</Version>
<PackageProjectUrl>https://github.com/Stone-Red-Code/DesktopMagic</PackageProjectUrl>
<RepositoryUrl>https://github.com/Stone-Red-Code/DesktopMagic</RepositoryUrl>
<AssemblyVersion>0.0.4.0</AssemblyVersion>
<FileVersion>0.0.4.0</FileVersion>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>1.0.0.0</FileVersion>
<TargetFramework>net8.0-windows7.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
+1 -1
View File
@@ -15,7 +15,7 @@ public partial class ColorDialog : Window
{
public System.Drawing.Color ResultColor { get; private set; }
public Brush ResultBrush { get; private set; }
public Brush ResultBrush { get; private set; } = Brushes.Transparent;
public ColorDialog(string content, System.Drawing.Color defaultColor, string title = "ColorDialog")
{
+3 -2
View File
@@ -573,15 +573,16 @@ namespace DesktopMagic
// Load plugins
foreach (uint pluginId in plugins.Keys)
{
InternalPluginData internalPluginData = plugins[pluginId];
// Add plugin to layout if it doesn't exist
if (!Settings.CurrentLayout.Plugins.TryGetValue(pluginId, out PluginSettings? pluginSettings))
{
Settings.CurrentLayout.Plugins.Add(pluginId, new PluginSettings());
Settings.CurrentLayout.Plugins.Add(pluginId, new PluginSettings() { Name = internalPluginData.Metadata.Name });
continue;
}
InternalPluginData internalPluginData = plugins[pluginId];
pluginSettings.Name = internalPluginData.Metadata.Name;
if (pluginSettings.Enabled)
+3 -2
View File
@@ -10,7 +10,8 @@
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
Background="{DynamicResource MaterialDesignPaper}"
FontFamily="{DynamicResource MaterialDesignFont}">
FontFamily="{DynamicResource MaterialDesignFont}"
Visibility="{Binding Visibility}">
<Grid Background="White" Margin="10">
<Grid.RowDefinitions>
<RowDefinition />
@@ -36,7 +37,7 @@
<StackPanel Grid.Row="1" Grid.ColumnSpan="2">
<Label Content="{Binding Name}" FontSize="20" />
<Label Content="{Binding Description}" />
<TextBlock Text="{Binding Description}" TextWrapping="Wrap" />
</StackPanel>
</Grid>
</UserControl>
+40 -6
View File
@@ -33,9 +33,11 @@
<Label Content="All plugins" FontSize="20" />
<TextBox Grid.Row="1" materialDesign:HintAssist.Hint="Search..." materialDesign:TextFieldAssist.HasLeadingIcon="True" materialDesign:TextFieldAssist.LeadingIcon="Search" />
<TextBox Grid.Row="1" Text="{Binding AllPluginsSearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" TextChanged="AllPluginsSearchTextBox_TextChanged" materialDesign:HintAssist.Hint="Search..." materialDesign:TextFieldAssist.HasLeadingIcon="True" materialDesign:TextFieldAssist.LeadingIcon="Search" />
<ItemsControl Grid.Row="2" ItemsSource="{Binding AllPlugins}">
<busyIndicator:BusyMask Grid.Row="2" IsBusy="{Binding IsSearching}" IndicatorType="Ring" BusyContent="Searching..." BusyContentMargin="0,50,0,0" IsBusyAtStartup="False">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding AllPlugins}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="LightGray" BorderThickness="1">
@@ -43,11 +45,28 @@
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.Style>
<Style TargetType="ItemsControl">
<Style.Triggers>
<Trigger Property="HasItems" Value="false">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<TextBlock Text="No plugins found!" FontSize="20" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</ItemsControl.Style>
</ItemsControl>
</ScrollViewer>
</busyIndicator:BusyMask>
<Label Grid.Column="1" Content="Installed plugins" FontSize="20" />
<TextBox Grid.Row="1" Grid.Column="1" materialDesign:HintAssist.Hint="Search..." materialDesign:TextFieldAssist.HasLeadingIcon="True" materialDesign:TextFieldAssist.LeadingIcon="Search" />
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding InstalledPluginsSearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" TextChanged="InstalledPluginsSearchTextBox_TextChanged" materialDesign:HintAssist.Hint="Search..." materialDesign:TextFieldAssist.HasLeadingIcon="True" materialDesign:TextFieldAssist.LeadingIcon="Search" />
<ItemsControl Grid.Row="2" Grid.Column="1" ItemsSource="{Binding InstalledPlugins}">
<ItemsControl.ItemTemplate>
@@ -57,11 +76,26 @@
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.Style>
<Style TargetType="ItemsControl">
<Style.Triggers>
<Trigger Property="HasItems" Value="false">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<TextBlock Text="No plugins found!" FontSize="20" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</ItemsControl.Style>
</ItemsControl>
<DockPanel Grid.Row="3" Grid.ColumnSpan="2">
<Image Source="{StaticResource ModioLogoBlueDark}" Height="30" HorizontalAlignment="Left" />
</DockPanel>
<Border Grid.Row="3" Grid.ColumnSpan="2" Padding="0 5 0 0" BorderThickness="0 2 0 0" BorderBrush="DarkGray">
<Image Source="{StaticResource ModioLogoBlueDark}" Cursor="Hand" Height="30" MouseUp="Image_MouseUp" HorizontalAlignment="Left" />
</Border>
</Grid>
</busyIndicator:BusyMask>
</Window>
+79 -3
View File
@@ -2,6 +2,7 @@
using DesktopMagic.Helpers;
using Modio.NET;
using Modio.NET.Filters;
using Modio.NET.Models;
using System;
@@ -15,6 +16,7 @@ using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
using File = System.IO.File;
using Path = System.IO.Path;
@@ -26,17 +28,28 @@ namespace DesktopMagic.Plugins;
/// </summary>
public partial class PluginManager : Window
{
private const int modIoGameId = 5665;
private readonly Client client = new Client(new Credentials("88e6ea774c3a502b06114e7fee0829ac"));
private readonly HttpClient httpClient = new();
private readonly PluginManagerDataContext pluginManagerDataContext = new();
private readonly string pluginsPath = Path.Combine(App.ApplicationDataPath, "Plugins");
private readonly DispatcherTimer searchTimer = new()
{
Interval = TimeSpan.FromMilliseconds(300),
};
public PluginManager()
{
InitializeComponent();
DataContext = pluginManagerDataContext;
searchTimer.Tick += async (sender, e) =>
{
searchTimer.Stop();
await SearchAllPlugins(pluginManagerDataContext.AllPluginsSearchText);
};
}
public void Remove(string pluginPath, uint? id)
@@ -81,7 +94,9 @@ public partial class PluginManager : Window
}
}
IAsyncEnumerable<Mod> mods = client.Games[5665].Mods.Search().ToEnumerableAsync();
Filter filter = ModFilter.Popular.Desc().Limit(100);
IAsyncEnumerable<Mod> mods = client.Games[modIoGameId].Mods.Search(filter).ToEnumerableAsync();
await foreach (Mod mod in mods)
{
if (pluginIds.Contains(mod.Id))
@@ -91,6 +106,8 @@ public partial class PluginManager : Window
pluginManagerDataContext.AllPlugins.Add(new PluginEntryDataContext(new(mod), new CommandHandler(async () => await Install(mod))));
}
pluginManagerDataContext.IsLoading = false;
}
protected override void OnClosing(CancelEventArgs e)
@@ -102,8 +119,6 @@ public partial class PluginManager : Window
{
pluginManagerDataContext.IsLoading = true;
Debug.WriteLine(mod.Modfile?.Download?.BinaryUrl + " | " + pluginsPath);
if (mod.Modfile?.Download?.BinaryUrl is null)
{
return;
@@ -149,4 +164,65 @@ public partial class PluginManager : Window
pluginManagerDataContext.IsLoading = false;
}
private void Image_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
string uri = "https://mod.io/g/DesktopMagic";
ProcessStartInfo psi = new ProcessStartInfo
{
UseShellExecute = true,
FileName = uri
};
_ = Process.Start(psi);
}
private void AllPluginsSearchTextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
{
pluginManagerDataContext.IsSearching = true;
searchTimer.Stop();
searchTimer.Start();
}
private void InstalledPluginsSearchTextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
{
if (string.IsNullOrWhiteSpace(pluginManagerDataContext.InstalledPluginsSearchText))
{
foreach (PluginEntryDataContext pluginEntryDataContext in pluginManagerDataContext.InstalledPlugins)
{
pluginEntryDataContext.IsVisible = true;
}
}
else
{
foreach (PluginEntryDataContext pluginEntryDataContext in pluginManagerDataContext.InstalledPlugins)
{
pluginEntryDataContext.IsVisible = pluginEntryDataContext.Name.Contains(pluginManagerDataContext.InstalledPluginsSearchText, StringComparison.OrdinalIgnoreCase);
}
}
}
private async Task SearchAllPlugins(string searchString)
{
pluginManagerDataContext.AllPlugins.Clear();
if (!searchString.Contains('*'))
{
searchString = $"*{searchString.Trim()}*";
}
Filter filter = ModFilter.Name.Like($"{searchString}").And(ModFilter.Popular.Desc()).Limit(100);
IAsyncEnumerable<Mod> mods = client.Games[modIoGameId].Mods.Search(filter).ToEnumerableAsync();
await foreach (Mod mod in mods)
{
if (pluginManagerDataContext.InstalledPlugins.Any(p => p.Id == mod.Id))
{
continue;
}
pluginManagerDataContext.AllPlugins.Add(new PluginEntryDataContext(new(mod), new CommandHandler(async () => await Install(mod))));
}
pluginManagerDataContext.IsSearching = false;
}
}