Skip to content

Commit 4d48b54

Browse files
committed
Feature: Search
1 parent 56ce6be commit 4d48b54

File tree

1 file changed

+171
-40
lines changed

1 file changed

+171
-40
lines changed

Source/NETworkManager/ViewModels/ProfilesViewModel.cs

Lines changed: 171 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Collections;
66
using System.Collections.Generic;
7+
using System.Collections.ObjectModel;
78
using System.ComponentModel;
89
using System.Linq;
910
using System.Windows;
@@ -19,8 +20,12 @@ public class ProfilesViewModel : ViewModelBase, IProfileManager
1920

2021
public ProfilesViewModel()
2122
{
22-
SetGroupsView();
23+
ProfileFilterTagsView = CollectionViewSource.GetDefaultView(ProfileFilterTags);
24+
ProfileFilterTagsView.SortDescriptions.Add(new SortDescription(nameof(ProfileFilterTagsInfo.Name),
25+
ListSortDirection.Ascending));
2326

27+
SetGroupsView();
28+
2429
ProfileManager.OnProfilesUpdated += ProfileManager_OnProfilesUpdated;
2530

2631
_searchDispatcherTimer.Interval = GlobalStaticConfiguration.SearchDispatcherTimerTimeSpan;
@@ -32,6 +37,7 @@ public ProfilesViewModel()
3237
#region Variables
3338

3439
private readonly DispatcherTimer _searchDispatcherTimer = new();
40+
private bool _searchDisabled;
3541

3642
private bool _isViewActive = true;
3743

@@ -66,14 +72,30 @@ public GroupInfo SelectedGroup
6672

6773
// NullReferenceException occurs if profile file is changed
6874
if (value != null)
69-
SetProfilesView(value, _lastSelectedProfileOnRefresh);
75+
{
76+
// Set/update tags based on current group
77+
CreateTags();
78+
79+
var filter = new ProfileFilterInfo
80+
{
81+
Search = Search,
82+
Tags = [.. ProfileFilterTags.Where(x => x.IsSelected).Select(x => x.Name)],
83+
TagsFilterMatch = ProfileFilterTagsMatchAny ? ProfileFilterTagsMatch.Any : ProfileFilterTagsMatch.All
84+
};
85+
86+
SetProfilesView(filter ,value, _lastSelectedProfileOnRefresh);
87+
88+
IsProfileFilterSet = !string.IsNullOrEmpty(filter.Search) || filter.Tags.Any();
89+
}
7090
else
91+
{
7192
Profiles = null;
93+
}
7294

7395
OnPropertyChanged();
7496
}
7597
}
76-
98+
7799
private ICollectionView _profiles;
78100

79101
public ICollectionView Profiles
@@ -132,8 +154,11 @@ public string Search
132154
_search = value;
133155

134156
// Start searching...
135-
IsSearching = true;
136-
_searchDispatcherTimer.Start();
157+
if (!_searchDisabled)
158+
{
159+
IsSearching = true;
160+
_searchDispatcherTimer.Start();
161+
}
137162

138163
OnPropertyChanged();
139164
}
@@ -154,11 +179,94 @@ public bool IsSearching
154179
}
155180
}
156181

157-
182+
private bool _profileFilterIsOpen;
183+
184+
public bool ProfileFilterIsOpen
185+
{
186+
get => _profileFilterIsOpen;
187+
set
188+
{
189+
if (value == _profileFilterIsOpen)
190+
return;
191+
192+
_profileFilterIsOpen = value;
193+
OnPropertyChanged();
194+
}
195+
}
196+
197+
public ICollectionView ProfileFilterTagsView { get; }
198+
199+
private ObservableCollection<ProfileFilterTagsInfo> ProfileFilterTags { get; } = [];
200+
201+
private bool _profileFilterTagsMatchAny = GlobalStaticConfiguration.Profile_TagsMatchAny;
202+
203+
public bool ProfileFilterTagsMatchAny
204+
{
205+
get => _profileFilterTagsMatchAny;
206+
set
207+
{
208+
if (value == _profileFilterTagsMatchAny)
209+
return;
210+
211+
_profileFilterTagsMatchAny = value;
212+
OnPropertyChanged();
213+
}
214+
}
215+
216+
private bool _profileFilterTagsMatchAll;
217+
218+
public bool ProfileFilterTagsMatchAll
219+
{
220+
get => _profileFilterTagsMatchAll;
221+
set
222+
{
223+
if (value == _profileFilterTagsMatchAll)
224+
return;
225+
226+
_profileFilterTagsMatchAll = value;
227+
OnPropertyChanged();
228+
}
229+
}
230+
231+
private bool _isProfileFilterSet;
232+
233+
public bool IsProfileFilterSet
234+
{
235+
get => _isProfileFilterSet;
236+
set
237+
{
238+
if (value == _isProfileFilterSet)
239+
return;
240+
241+
_isProfileFilterSet = value;
242+
OnPropertyChanged();
243+
}
244+
}
158245
#endregion
159246

160247
#region Commands & Actions
161248

249+
public ICommand AddGroupCommand => new RelayCommand(_ => AddGroupAction());
250+
251+
private void AddGroupAction()
252+
{
253+
ProfileDialogManager.ShowAddGroupDialog(Application.Current.MainWindow, this).ConfigureAwait(false);
254+
}
255+
256+
public ICommand EditGroupCommand => new RelayCommand(_ => EditGroupAction());
257+
258+
private void EditGroupAction()
259+
{
260+
ProfileDialogManager.ShowEditGroupDialog(Application.Current.MainWindow, this, SelectedGroup).ConfigureAwait(false);
261+
}
262+
263+
public ICommand DeleteGroupCommand => new RelayCommand(_ => DeleteGroupAction());
264+
265+
private void DeleteGroupAction()
266+
{
267+
ProfileDialogManager.ShowDeleteGroupDialog(Application.Current.MainWindow, this, SelectedGroup).ConfigureAwait(false);
268+
}
269+
162270
public ICommand AddProfileCommand => new RelayCommand(_ => AddProfileAction());
163271

164272
private void AddProfileAction()
@@ -200,27 +308,38 @@ private void DeleteProfileAction()
200308
.ConfigureAwait(false);
201309
}
202310

203-
public ICommand AddGroupCommand => new RelayCommand(_ => AddGroupAction());
311+
public ICommand OpenProfileFilterCommand => new RelayCommand(_ => OpenProfileFilterAction());
204312

205-
private void AddGroupAction()
313+
private void OpenProfileFilterAction()
206314
{
207-
ProfileDialogManager.ShowAddGroupDialog(Application.Current.MainWindow, this).ConfigureAwait(false);
315+
ProfileFilterIsOpen = true;
208316
}
209317

210-
public ICommand EditGroupCommand => new RelayCommand(_ => EditGroupAction());
318+
public ICommand ApplyProfileFilterCommand => new RelayCommand(_ => ApplyProfileFilterAction());
211319

212-
private void EditGroupAction()
320+
private void ApplyProfileFilterAction()
213321
{
214-
ProfileDialogManager.ShowEditGroupDialog(Application.Current.MainWindow, this, SelectedGroup).ConfigureAwait(false);
322+
RefreshProfiles();
323+
324+
ProfileFilterIsOpen = false;
215325
}
216326

217-
public ICommand DeleteGroupCommand => new RelayCommand(_ => DeleteGroupAction());
327+
public ICommand ClearProfileFilterCommand => new RelayCommand(_ => ClearProfileFilterAction());
218328

219-
private void DeleteGroupAction()
329+
private void ClearProfileFilterAction()
220330
{
221-
ProfileDialogManager.ShowDeleteGroupDialog(Application.Current.MainWindow, this, SelectedGroup).ConfigureAwait(false);
331+
_searchDisabled = true;
332+
Search = string.Empty;
333+
_searchDisabled = false;
334+
335+
foreach (var tag in ProfileFilterTags)
336+
tag.IsSelected = false;
337+
338+
RefreshProfiles();
339+
340+
IsProfileFilterSet = false;
341+
ProfileFilterIsOpen = false;
222342
}
223-
224343
#endregion
225344

226345
#region Methods
@@ -240,7 +359,9 @@ public void OnViewHide()
240359
private void SetGroupsView(GroupInfo group = null)
241360
{
242361
Groups = new CollectionViewSource
243-
{ Source = ProfileManager.Groups.Where(x => !x.IsDynamic).OrderBy(x => x.Name) }.View;
362+
{
363+
Source = ProfileManager.Groups.Where(x => !x.IsDynamic).OrderBy(x => x.Name)
364+
}.View;
244365

245366
// Set specific group or first if null
246367
SelectedGroup = null;
@@ -252,34 +373,44 @@ private void SetGroupsView(GroupInfo group = null)
252373
SelectedGroup = Groups.SourceCollection.Cast<GroupInfo>().MinBy(x => x.Name);
253374
}
254375

255-
private void SetProfilesView(GroupInfo group, ProfileInfo profile = null)
376+
private void CreateTags()
256377
{
257-
Profiles = new CollectionViewSource
258-
{
259-
Source = ProfileManager.Groups.FirstOrDefault(x => x.Equals(group))?.Profiles.Where(x => !x.IsDynamic)
260-
.OrderBy(x => x.Name)
261-
}.View;
378+
// Get all tags from profiles in the selected group
379+
var tags = ProfileManager.Groups.First(x => x.Name == SelectedGroup.Name).Profiles
380+
.SelectMany(x => x.TagsCollection).Distinct().ToList();
381+
382+
var tagSet = new HashSet<string>(tags);
262383

263-
Profiles.Filter = o =>
384+
for (var i = ProfileFilterTags.Count - 1; i >= 0; i--)
264385
{
265-
if (string.IsNullOrEmpty(Search))
266-
return true;
267-
268-
if (o is not ProfileInfo info)
269-
return false;
270-
271-
var search = Search.Trim();
386+
if (!tagSet.Contains(ProfileFilterTags[i].Name))
387+
ProfileFilterTags.RemoveAt(i);
388+
}
272389

273-
// Search by: Tag=xxx (exact match, ignore case)
274-
/*
275-
if (search.StartsWith(ProfileManager.TagIdentifier, StringComparison.OrdinalIgnoreCase))
276-
return !string.IsNullOrEmpty(info.Tags) && info.Tags.Replace(" ", "").Split(';').Any(str => search.Substring(ProfileManager.TagIdentifier.Length, search.Length - ProfileManager.TagIdentifier.Length).Equals(str, StringComparison.OrdinalIgnoreCase));
277-
*/
390+
var existingTagNames = new HashSet<string>(ProfileFilterTags.Select(ft => ft.Name));
278391

279-
// Search by: Name, Host
280-
return info.Name.IndexOf(search, StringComparison.OrdinalIgnoreCase) > -1 ||
281-
info.Host.IndexOf(search, StringComparison.OrdinalIgnoreCase) > -1;
282-
};
392+
foreach (var tag in tags.Where(tag => !existingTagNames.Contains(tag)))
393+
{
394+
ProfileFilterTags.Add(new ProfileFilterTagsInfo(false, tag));
395+
}
396+
}
397+
398+
private void SetProfilesView(ProfileFilterInfo filter, GroupInfo group, ProfileInfo profile = null)
399+
{
400+
Profiles = new CollectionViewSource
401+
{
402+
Source = ProfileManager.Groups.FirstOrDefault(x => x.Equals(group))?.Profiles.Where(x => !x.IsDynamic && (
403+
string.IsNullOrEmpty(Search) || x.Name.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1) && (
404+
// If no tags are selected, show all profiles
405+
(!filter.Tags.Any()) ||
406+
// Any tag can match
407+
(filter.TagsFilterMatch == ProfileFilterTagsMatch.Any &&
408+
filter.Tags.Any(tag => x.TagsCollection.Contains(tag))) ||
409+
// All tags must match
410+
(filter.TagsFilterMatch == ProfileFilterTagsMatch.All &&
411+
filter.Tags.All(tag => x.TagsCollection.Contains(tag))))
412+
).OrderBy(x => x.Name)
413+
}.View;
283414

284415
// Set specific profile or first if null
285416
SelectedProfile = null;

0 commit comments

Comments
 (0)