Skip to content

Commit 7fdf3a2

Browse files
authored
Merge pull request #4398 from DavidGBrett/sorting-options-on-plugin-store
[FEATURE] Add sorting modes to the plugin store
2 parents f6b200b + 6f44d0c commit 7fdf3a2

File tree

6 files changed

+206
-74
lines changed

6 files changed

+206
-74
lines changed

.github/actions/spelling/expect.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,7 @@ Msix
108108
dummyprofile
109109
browserbookmark
110110
copyurl
111+
uninstaller
112+
taskbar
113+
taskbars
114+
ikw

Flow.Launcher/Languages/en.xaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,11 @@
242242
<system:String x:Key="pluginStore_RecentlyUpdated">Recently Updated</system:String>
243243
<system:String x:Key="pluginStore_None">Plugins</system:String>
244244
<system:String x:Key="pluginStore_Installed">Installed</system:String>
245+
<system:String x:Key="PluginStoreSortModeDefault">Default</system:String>
246+
<system:String x:Key="PluginStoreSortModeName">Name</system:String>
247+
<system:String x:Key="PluginStoreSortModeReleaseDate">Release Date</system:String>
248+
<system:String x:Key="PluginStoreSortModeUpdatedDate">Updated Date</system:String>
249+
<system:String x:Key="PluginStoreSortingModeComboboxLabel">Sorting Mode:</system:String>
245250
<system:String x:Key="refresh">Refresh</system:String>
246251
<system:String x:Key="installbtn">Install</system:String>
247252
<system:String x:Key="uninstallbtn">Uninstall</system:String>

Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs

Lines changed: 71 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,31 @@ namespace Flow.Launcher.SettingPages.ViewModels;
1212

1313
public partial class SettingsPanePluginStoreViewModel : BaseModel
1414
{
15+
public class SortModeData : DropdownDataGeneric<PluginStoreSortMode> { }
16+
17+
public List<SortModeData> SortModes { get; } =
18+
DropdownDataGeneric<PluginStoreSortMode>.GetValues<SortModeData>("PluginStoreSortMode");
19+
20+
public SettingsPanePluginStoreViewModel()
21+
{
22+
UpdateEnumDropdownLocalizations();
23+
}
24+
25+
private PluginStoreSortMode _selectedSortMode = PluginStoreSortMode.Default;
26+
public PluginStoreSortMode SelectedSortMode
27+
{
28+
get => _selectedSortMode;
29+
set
30+
{
31+
if (_selectedSortMode != value)
32+
{
33+
_selectedSortMode = value;
34+
OnPropertyChanged();
35+
OnPropertyChanged(nameof(ExternalPlugins));
36+
}
37+
}
38+
}
39+
1540
private string filterText = string.Empty;
1641
public string FilterText
1742
{
@@ -82,13 +107,8 @@ public bool ShowExecutable
82107
}
83108
}
84109

85-
public IList<PluginStoreItemViewModel> ExternalPlugins => App.API.GetPluginManifest()
86-
.Select(p => new PluginStoreItemViewModel(p))
87-
.OrderByDescending(p => p.Category == PluginStoreItemViewModel.NewRelease)
88-
.ThenByDescending(p => p.Category == PluginStoreItemViewModel.RecentlyUpdated)
89-
.ThenByDescending(p => p.Category == PluginStoreItemViewModel.None)
90-
.ThenByDescending(p => p.Category == PluginStoreItemViewModel.Installed)
91-
.ToList();
110+
public IList<PluginStoreItemViewModel> ExternalPlugins => GetSortedPlugins(
111+
App.API.GetPluginManifest().Select(p => new PluginStoreItemViewModel(p)));
92112

93113
[RelayCommand]
94114
private async Task RefreshExternalPluginsAsync()
@@ -168,4 +188,48 @@ public bool SatisfiesFilter(PluginStoreItemViewModel plugin)
168188
App.API.FuzzySearch(FilterText, plugin.Name).IsSearchPrecisionScoreMet() ||
169189
App.API.FuzzySearch(FilterText, plugin.Description).IsSearchPrecisionScoreMet();
170190
}
191+
192+
private void UpdateEnumDropdownLocalizations()
193+
{
194+
DropdownDataGeneric<PluginStoreSortMode>.UpdateLabels(SortModes);
195+
}
196+
197+
private IList<PluginStoreItemViewModel> GetSortedPlugins(IEnumerable<PluginStoreItemViewModel> plugins)
198+
{
199+
return SelectedSortMode switch
200+
{
201+
PluginStoreSortMode.Name => plugins
202+
.OrderBy(p => p.LabelInstalled)
203+
.ThenBy(p => p.Name)
204+
.ToList(),
205+
206+
PluginStoreSortMode.ReleaseDate => plugins
207+
.OrderBy(p => p.LabelInstalled)
208+
.ThenByDescending(p => p.DateAdded.HasValue)
209+
.ThenByDescending(p => p.DateAdded)
210+
.ToList(),
211+
212+
PluginStoreSortMode.UpdatedDate => plugins
213+
.OrderBy(p => p.LabelInstalled)
214+
.ThenByDescending(p => p.UpdatedDate.HasValue)
215+
.ThenByDescending(p => p.UpdatedDate)
216+
.ToList(),
217+
218+
_ => plugins
219+
.OrderByDescending(p => p.DefaultCategory == PluginStoreItemViewModel.NewRelease)
220+
.ThenByDescending(p => p.DefaultCategory == PluginStoreItemViewModel.RecentlyUpdated)
221+
.ThenByDescending(p => p.DefaultCategory == PluginStoreItemViewModel.None)
222+
.ThenByDescending(p => p.DefaultCategory == PluginStoreItemViewModel.Installed)
223+
.ToList(),
224+
};
225+
}
226+
227+
}
228+
229+
public enum PluginStoreSortMode
230+
{
231+
Default,
232+
Name,
233+
ReleaseDate,
234+
UpdatedDate
171235
}

Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml

Lines changed: 93 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
Filter="PluginStoreCollectionView_OnFilter"
2323
Source="{Binding ExternalPlugins}">
2424
<CollectionViewSource.GroupDescriptions>
25-
<PropertyGroupDescription PropertyName="Category" />
25+
<PropertyGroupDescription PropertyName="DefaultCategory" />
2626
</CollectionViewSource.GroupDescriptions>
2727
</CollectionViewSource>
2828
</ui:Page.Resources>
@@ -32,7 +32,7 @@
3232
<ColumnDefinition Width="*" />
3333
</Grid.ColumnDefinitions>
3434
<Grid.RowDefinitions>
35-
<RowDefinition Height="72" />
35+
<RowDefinition Height="Auto" />
3636
<RowDefinition Height="*" />
3737
</Grid.RowDefinitions>
3838
<Border
@@ -50,77 +50,104 @@
5050
<DockPanel
5151
Grid.Row="0"
5252
Grid.Column="1"
53-
Margin="5 24 0 0">
54-
53+
Margin="5 24 0 8">
5554
<ikw:SimpleStackPanel
5655
HorizontalAlignment="Right"
5756
VerticalAlignment="Center"
5857
DockPanel.Dock="Right"
59-
Orientation="Horizontal"
58+
Orientation="Vertical"
6059
Spacing="8">
61-
<Button
62-
Height="34"
63-
Margin="0 5 0 5"
64-
Padding="12 4"
60+
<ikw:SimpleStackPanel
6561
HorizontalAlignment="Right"
66-
VerticalAlignment="Center"
67-
Command="{Binding RefreshExternalPluginsCommand}"
68-
Content="{DynamicResource refresh}"
69-
FontSize="13" />
70-
<Button Height="34">
71-
<ui:FontIcon FontSize="14" Glyph="&#xe71c;" />
72-
<ui:FlyoutService.Flyout>
73-
<ui:MenuFlyout x:Name="FilterFlyout" Placement="Bottom">
74-
<MenuItem
75-
Header=".Net"
76-
IsCheckable="True"
77-
IsChecked="{Binding ShowDotNet, Mode=TwoWay}"
78-
StaysOpenOnClick="True" />
79-
<MenuItem
80-
Header="Python"
81-
IsCheckable="True"
82-
IsChecked="{Binding ShowPython, Mode=TwoWay}"
83-
StaysOpenOnClick="True" />
84-
<MenuItem
85-
Header="Node.js"
86-
IsCheckable="True"
87-
IsChecked="{Binding ShowNodeJs, Mode=TwoWay}"
88-
StaysOpenOnClick="True" />
89-
<MenuItem
90-
Header="Exe"
91-
IsCheckable="True"
92-
IsChecked="{Binding ShowExecutable, Mode=TwoWay}"
93-
StaysOpenOnClick="True" />
94-
</ui:MenuFlyout>
95-
</ui:FlyoutService.Flyout>
96-
</Button>
97-
<Button
98-
Height="34"
99-
Command="{Binding InstallPluginCommand}"
100-
ToolTip="{DynamicResource installLocalPluginTooltip}">
101-
<ui:FontIcon FontSize="14" Glyph="&#xE8DA;" />
102-
</Button>
103-
<Button
104-
Height="34"
105-
Command="{Binding CheckPluginUpdatesCommand}"
106-
ToolTip="{DynamicResource checkPluginUpdatesTooltip}">
107-
<ui:FontIcon FontSize="14" Glyph="&#xecc5;" />
108-
</Button>
109-
<TextBox
110-
Name="PluginStoreFilterTextbox"
111-
Width="150"
112-
Height="34"
113-
Margin="0 0 26 0"
62+
Orientation="Horizontal"
63+
Spacing="8">
64+
<Button
65+
Height="34"
66+
Padding="12 4"
67+
HorizontalAlignment="Right"
68+
VerticalAlignment="Center"
69+
Command="{Binding RefreshExternalPluginsCommand}"
70+
Content="{DynamicResource refresh}"
71+
FontSize="13" />
72+
<Button Height="34">
73+
<ui:FontIcon FontSize="14" Glyph="&#xe71c;" />
74+
<ui:FlyoutService.Flyout>
75+
<ui:MenuFlyout x:Name="FilterFlyout" Placement="Bottom">
76+
<MenuItem
77+
Header=".Net"
78+
IsCheckable="True"
79+
IsChecked="{Binding ShowDotNet, Mode=TwoWay}"
80+
StaysOpenOnClick="True" />
81+
<MenuItem
82+
Header="Python"
83+
IsCheckable="True"
84+
IsChecked="{Binding ShowPython, Mode=TwoWay}"
85+
StaysOpenOnClick="True" />
86+
<MenuItem
87+
Header="Node.js"
88+
IsCheckable="True"
89+
IsChecked="{Binding ShowNodeJs, Mode=TwoWay}"
90+
StaysOpenOnClick="True" />
91+
<MenuItem
92+
Header="Exe"
93+
IsCheckable="True"
94+
IsChecked="{Binding ShowExecutable, Mode=TwoWay}"
95+
StaysOpenOnClick="True" />
96+
</ui:MenuFlyout>
97+
</ui:FlyoutService.Flyout>
98+
</Button>
99+
<Button
100+
Height="34"
101+
Command="{Binding InstallPluginCommand}"
102+
ToolTip="{DynamicResource installLocalPluginTooltip}">
103+
<ui:FontIcon FontSize="14" Glyph="&#xE8DA;" />
104+
</Button>
105+
<Button
106+
Height="34"
107+
Command="{Binding CheckPluginUpdatesCommand}"
108+
ToolTip="{DynamicResource checkPluginUpdatesTooltip}">
109+
<ui:FontIcon FontSize="14" Glyph="&#xecc5;" />
110+
</Button>
111+
<TextBox
112+
Name="PluginStoreFilterTextbox"
113+
Width="150"
114+
Height="34"
115+
Margin="0 0 26 0"
116+
HorizontalAlignment="Right"
117+
VerticalContentAlignment="Center"
118+
ui:ControlHelper.PlaceholderText="{DynamicResource searchplugin}"
119+
ContextMenu="{StaticResource TextBoxContextMenu}"
120+
DockPanel.Dock="Right"
121+
FontSize="14"
122+
Text="{Binding FilterText, UpdateSourceTrigger=PropertyChanged}"
123+
ToolTip="{DynamicResource searchpluginToolTip}"
124+
ToolTipService.InitialShowDelay="200"
125+
ToolTipService.Placement="Top" />
126+
</ikw:SimpleStackPanel>
127+
128+
<ikw:SimpleStackPanel
114129
HorizontalAlignment="Right"
115-
VerticalContentAlignment="Center"
116-
ui:ControlHelper.PlaceholderText="{DynamicResource searchplugin}"
117-
ContextMenu="{StaticResource TextBoxContextMenu}"
118-
DockPanel.Dock="Right"
119-
FontSize="14"
120-
Text="{Binding FilterText, UpdateSourceTrigger=PropertyChanged}"
121-
ToolTip="{DynamicResource searchpluginToolTip}"
122-
ToolTipService.InitialShowDelay="200"
123-
ToolTipService.Placement="Top" />
130+
Margin="0 0 26 0"
131+
Orientation="Horizontal"
132+
Spacing="8">
133+
<TextBlock
134+
VerticalAlignment="Center"
135+
FontSize="14"
136+
Foreground="{DynamicResource Color15B}"
137+
Text="{DynamicResource PluginStoreSortingModeComboboxLabel}" />
138+
<ComboBox
139+
x:Name="SortModeComboBox"
140+
Width="Auto"
141+
Height="34"
142+
MinWidth="150"
143+
MaxWidth="150"
144+
HorizontalContentAlignment="Left"
145+
Background="{DynamicResource Color00B}"
146+
ItemsSource="{Binding SortModes}"
147+
DisplayMemberPath="Display"
148+
SelectedValuePath="Value"
149+
SelectedValue="{Binding SelectedSortMode, Mode=TwoWay}"/>
150+
</ikw:SimpleStackPanel>
124151
</ikw:SimpleStackPanel>
125152
</DockPanel>
126153

Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,28 @@ protected override void OnNavigatedTo(NavigationEventArgs e)
2929
{
3030
InitializeComponent();
3131
}
32+
UpdateCategoryGrouping();
3233
_viewModel.PropertyChanged += ViewModel_PropertyChanged;
3334
base.OnNavigatedTo(e);
3435
}
3536

3637
private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
3738
{
39+
// If SelectedSortMode changed, then we need to update the categories
40+
if (e.PropertyName == nameof(SettingsPanePluginStoreViewModel.SelectedSortMode))
41+
{
42+
UpdateCategoryGrouping();
43+
}
44+
45+
// Check if changed property requires PluginStoreCollectionView refresh
3846
switch (e.PropertyName)
3947
{
4048
case nameof(SettingsPanePluginStoreViewModel.FilterText):
4149
case nameof(SettingsPanePluginStoreViewModel.ShowDotNet):
4250
case nameof(SettingsPanePluginStoreViewModel.ShowPython):
4351
case nameof(SettingsPanePluginStoreViewModel.ShowNodeJs):
4452
case nameof(SettingsPanePluginStoreViewModel.ShowExecutable):
53+
case nameof(SettingsPanePluginStoreViewModel.SelectedSortMode):
4554
((CollectionViewSource)FindResource("PluginStoreCollectionView")).View.Refresh();
4655
break;
4756
}
@@ -75,4 +84,23 @@ private void PluginStoreCollectionView_OnFilter(object sender, FilterEventArgs e
7584

7685
e.Accepted = _viewModel.SatisfiesFilter(plugin);
7786
}
87+
88+
private void UpdateCategoryGrouping()
89+
{
90+
var collectionView = (CollectionViewSource)FindResource("PluginStoreCollectionView");
91+
var groupDescriptions = collectionView.GroupDescriptions;
92+
93+
groupDescriptions.Clear();
94+
95+
// For default sorting mode we use the default categories
96+
if (_viewModel.SelectedSortMode == PluginStoreSortMode.Default)
97+
{
98+
groupDescriptions.Add(new PropertyGroupDescription(nameof(PluginStoreItemViewModel.DefaultCategory)));
99+
}
100+
// Otherwise we only split by installed or not
101+
else
102+
{
103+
groupDescriptions.Add(new PropertyGroupDescription(nameof(PluginStoreItemViewModel.InstallCategory)));
104+
}
105+
}
78106
}

Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ public PluginStoreItemViewModel(UserPlugin plugin)
2828
public string UrlDownload => _newPlugin.UrlDownload;
2929
public string UrlSourceCode => _newPlugin.UrlSourceCode;
3030
public string IcoPath => _newPlugin.IcoPath;
31+
public DateTime? DateAdded => _newPlugin.DateAdded;
32+
public DateTime? UpdatedDate => _newPlugin.LatestReleaseDate;
3133

3234
public bool LabelInstalled => _oldPluginPair != null;
3335
public bool LabelUpdate => LabelInstalled && new Version(_newPlugin.Version) > new Version(_oldPluginPair.Metadata.Version);
@@ -37,7 +39,9 @@ public PluginStoreItemViewModel(UserPlugin plugin)
3739
internal const string NewRelease = "NewRelease";
3840
internal const string Installed = "Installed";
3941

40-
public string Category
42+
public string InstallCategory => LabelInstalled ? Installed : None;
43+
44+
public string DefaultCategory
4145
{
4246
get
4347
{

0 commit comments

Comments
 (0)