Implement plugin store settings pane

This commit is contained in:
Yusyuriv 2024-05-15 22:56:58 +06:00
parent 996ccf7976
commit 3bcb5da99c
No known key found for this signature in database
GPG key ID: A91C52E6F73148E0
4 changed files with 131 additions and 47 deletions

View file

@ -0,0 +1,37 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.Core.ExternalPlugins;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Plugin;
using Flow.Launcher.ViewModel;
namespace Flow.Launcher.SettingPages.ViewModels;
public partial class SettingsPanePluginStoreViewModel : BaseModel
{
public string FilterText { get; set; } = string.Empty;
public IList<PluginStoreItemViewModel> ExternalPlugins => PluginsManifest.UserPlugins
.Select(p => new PluginStoreItemViewModel(p))
.OrderByDescending(p => p.Category == PluginStoreItemViewModel.NewRelease)
.ThenByDescending(p => p.Category == PluginStoreItemViewModel.RecentlyUpdated)
.ThenByDescending(p => p.Category == PluginStoreItemViewModel.None)
.ThenByDescending(p => p.Category == PluginStoreItemViewModel.Installed)
.ToList();
[RelayCommand]
private async Task RefreshExternalPluginsAsync()
{
await PluginsManifest.UpdateManifestAsync();
OnPropertyChanged(nameof(ExternalPlugins));
}
public bool SatisfiesFilter(PluginStoreItemViewModel plugin)
{
return string.IsNullOrEmpty(FilterText) ||
StringMatcher.FuzzySearch(FilterText, plugin.Name).IsSearchPrecisionScoreMet() ||
StringMatcher.FuzzySearch(FilterText, plugin.Description).IsSearchPrecisionScoreMet();
}
}

View file

@ -1,25 +1,27 @@
<Page
<ui:Page
x:Class="Flow.Launcher.SettingPages.Views.SettingsPanePluginStore"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Flow.Launcher.SettingPages.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:ui="http://schemas.modernwpf.com/2019"
xmlns:wpftk="clr-namespace:WpfToolkit.Controls;assembly=VirtualizingWrapPanel"
xmlns:viewModels="clr-namespace:Flow.Launcher.SettingPages.ViewModels"
Title="PluginStore"
FocusManager.FocusedElement="{Binding ElementName=PluginStoreFilterTextbox}"
KeyDown="SettingsPanePlugins_OnKeyDown"
d:DataContext="{d:DesignInstance viewModels:SettingsPanePluginStoreViewModel}"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<Page.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Resources/SettingWindowStyle.xaml" />
</ResourceDictionary.MergedDictionaries>
<CollectionViewSource x:Key="SortedFonts" Source="{Binding Source={x:Static Fonts.SystemFontFamilies}}" />
</ResourceDictionary>
</Page.Resources>
<ui:Page.Resources>
<CollectionViewSource x:Key="PluginStoreCollectionView" Source="{Binding ExternalPlugins}" Filter="PluginStoreCollectionView_OnFilter">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Category" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</ui:Page.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
@ -38,7 +40,7 @@
FontSize="30"
Style="{StaticResource PageTitle}"
Text="{DynamicResource pluginStore}"
TextAlignment="left" />
TextAlignment="Left" />
</Border>
<DockPanel
@ -47,7 +49,7 @@
Margin="5,24,0,0">
<TextBox
Name="pluginStoreFilterTxb"
Name="PluginStoreFilterTextbox"
Width="150"
Height="34"
Margin="0,0,26,0"
@ -55,7 +57,7 @@
ContextMenu="{StaticResource TextBoxContextMenu}"
DockPanel.Dock="Right"
FontSize="14"
Text=""
Text="{Binding FilterText, UpdateSourceTrigger=PropertyChanged}"
TextAlignment="Left"
ToolTip="{DynamicResource searchpluginToolTip}"
ToolTipService.InitialShowDelay="200"
@ -117,8 +119,7 @@
VirtualizingPanel.CacheLength="1,1"
VirtualizingPanel.CacheLengthUnit="Page"
VirtualizingPanel.IsVirtualizingWhenGrouping="True"
VirtualizingPanel.ScrollUnit="Pixel"
VirtualizingPanel.VirtualizationMode="Standard">
VirtualizingPanel.ScrollUnit="Pixel">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<wpftk:VirtualizingWrapPanel
@ -254,7 +255,7 @@
Grid.Column="0"
Margin="5,4,0,0"
TextWrapping="Wrap">
<Hyperlink Foreground="{DynamicResource Color04B}" NavigateUri="{Binding Website}">
<Hyperlink Foreground="{DynamicResource Color04B}" NavigateUri="{Binding Website}" RequestNavigate="Hyperlink_OnRequestNavigate">
<Run FontSize="12" Text="{Binding Author, Mode=OneWay}" />
</Hyperlink>
</TextBlock>
@ -273,7 +274,9 @@
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Content="{DynamicResource installbtn}"
Visibility="{Binding LabelInstalled, Converter={StaticResource BoolToVisibilityConverter}, ConverterParameter='!'}" />
Visibility="{Binding LabelInstalled, Converter={StaticResource BoolToVisibilityConverter}, ConverterParameter='!'}"
Command="{Binding ShowCommandQueryCommand}"
CommandParameter="install" />
<Button
MinHeight="42"
Margin="5,0,5,0"
@ -281,7 +284,9 @@
HorizontalAlignment="Right"
VerticalAlignment="Center"
Content="{DynamicResource uninstallbtn}"
Visibility="{Binding LabelInstalled, Converter={StaticResource BoolToVisibilityConverter}}" />
Visibility="{Binding LabelInstalled, Converter={StaticResource BoolToVisibilityConverter}}"
Command="{Binding ShowCommandQueryCommand}"
CommandParameter="uninstall" />
<Button
MinHeight="42"
Margin="5,0,5,0"
@ -290,7 +295,9 @@
VerticalAlignment="Center"
Content="{DynamicResource updatebtn}"
Style="{DynamicResource AccentButtonStyle}"
Visibility="{Binding LabelUpdate, Converter={StaticResource BoolToVisibilityConverter}}" />
Visibility="{Binding LabelUpdate, Converter={StaticResource BoolToVisibilityConverter}}"
Command="{Binding ShowCommandQueryCommand}"
CommandParameter="update" />
</VirtualizingStackPanel>
</Grid>
</ui:Flyout>
@ -299,7 +306,6 @@
<StackPanel Width="200">
<StackPanel Orientation="Horizontal">
<Image
Grid.Column="0"
Width="32"
Height="32"
Margin="18,24,0,0"
@ -319,7 +325,6 @@
Visibility="{Binding LabelUpdate, Converter={StaticResource BoolToVisibilityConverter}}" />
</StackPanel>
<TextBlock
Grid.Column="0"
Margin="18,10,18,0"
FontWeight="SemiBold"
Foreground="{DynamicResource Color05B}"
@ -327,7 +332,6 @@
TextWrapping="Wrap"
ToolTip="{Binding Version}" />
<TextBlock
Grid.Column="0"
Height="60"
Margin="18,6,18,0"
Padding="0,0,0,10"
@ -345,4 +349,4 @@
</ListView.ItemTemplate>
</ListView>
</Grid>
</Page>
</ui:Page>

View file

@ -1,28 +1,66 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.ComponentModel;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.SettingPages.ViewModels;
using Flow.Launcher.ViewModel;
namespace Flow.Launcher.SettingPages.Views
namespace Flow.Launcher.SettingPages.Views;
public partial class SettingsPanePluginStore
{
/// <summary>
/// PluginStore.xaml에 대한 상호 작용 논리
/// </summary>
public partial class SettingsPanePluginStore : Page
private SettingsPanePluginStoreViewModel _viewModel = null!;
protected override void OnNavigatedTo(NavigationEventArgs e)
{
public SettingsPanePluginStore()
if (!IsInitialized)
{
if (e.ExtraData is not SettingWindow.PaneData { Settings: { } settings })
throw new ArgumentException($"Settings are required for {nameof(SettingsPanePluginStore)}.");
_viewModel = new SettingsPanePluginStoreViewModel();
DataContext = _viewModel;
InitializeComponent();
}
_viewModel.PropertyChanged += ViewModel_PropertyChanged;
base.OnNavigatedTo(e);
}
private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(SettingsPanePluginStoreViewModel.FilterText))
{
((CollectionViewSource)FindResource("PluginStoreCollectionView")).View.Refresh();
}
}
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
_viewModel.PropertyChanged -= ViewModel_PropertyChanged;
base.OnNavigatingFrom(e);
}
private void SettingsPanePlugins_OnKeyDown(object sender, KeyEventArgs e)
{
if (e.Key is not Key.F || Keyboard.Modifiers is not ModifierKeys.Control) return;
PluginStoreFilterTextbox.Focus();
}
private void Hyperlink_OnRequestNavigate(object sender, RequestNavigateEventArgs e)
{
PluginManager.API.OpenUrl(e.Uri.AbsoluteUri);
e.Handled = true;
}
private void PluginStoreCollectionView_OnFilter(object sender, FilterEventArgs e)
{
if (e.Item is not PluginStoreItemViewModel plugin)
{
e.Accepted = false;
return;
}
e.Accepted = _viewModel.SatisfiesFilter(plugin);
}
}

View file

@ -1,12 +1,15 @@
using System;
using System.Linq;
using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.Core.ExternalPlugins;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.ViewModel
{
public class PluginStoreItemViewModel : BaseModel
public partial class PluginStoreItemViewModel : BaseModel
{
private PluginPair PluginManagerData => PluginManager.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7");
public PluginStoreItemViewModel(UserPlugin plugin)
{
_plugin = plugin;
@ -26,19 +29,13 @@ namespace Flow.Launcher.ViewModel
public string IcoPath => _plugin.IcoPath;
public bool LabelInstalled => PluginManager.GetPluginForId(_plugin.ID) != null;
public bool LabelUpdate => LabelInstalled && VersionConvertor(_plugin.Version) > VersionConvertor(PluginManager.GetPluginForId(_plugin.ID).Metadata.Version);
public bool LabelUpdate => LabelInstalled && new Version(_plugin.Version) > new Version(PluginManager.GetPluginForId(_plugin.ID).Metadata.Version);
internal const string None = "None";
internal const string RecentlyUpdated = "RecentlyUpdated";
internal const string NewRelease = "NewRelease";
internal const string Installed = "Installed";
public Version VersionConvertor(string version)
{
Version ResultVersion = new Version(version);
return ResultVersion;
}
public string Category
{
get
@ -60,5 +57,13 @@ namespace Flow.Launcher.ViewModel
return category;
}
}
[RelayCommand]
private void ShowCommandQuery(string action)
{
var actionKeyword = PluginManagerData.Metadata.ActionKeywords.Any() ? PluginManagerData.Metadata.ActionKeywords[0] + " " : String.Empty;
App.API.ChangeQuery($"{actionKeyword}{action} {_plugin.Name}");
App.API.ShowMainWindow();
}
}
}