Skip to content

Commit

Permalink
Moved search results to AutoSuggestedBox instead of NavigationView (#105
Browse files Browse the repository at this point in the history
)
  • Loading branch information
veler authored Dec 6, 2021
1 parent 51e861d commit 51d88a6
Show file tree
Hide file tree
Showing 15 changed files with 235 additions and 49 deletions.
11 changes: 7 additions & 4 deletions src/dev/impl/DevToys/Api/Tools/MatchedToolProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,11 @@ public MatchSpan[] MatchedSpans
set
{
_matchedSpans = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(AnyMatchedSpan)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(MatchedSpans)));
ThreadHelper.RunOnUIThreadAsync(() =>
{
RaisePropertyChanged(nameof(AnyMatchedSpan));
RaisePropertyChanged(nameof(MatchedSpans));
}).Forget();
}
}

Expand Down Expand Up @@ -73,11 +76,11 @@ public IReadOnlyList<MatchedToolProvider> ChildrenTools

public event PropertyChangedEventHandler? PropertyChanged;

public MatchedToolProvider(ToolProviderMetadata metadata, IToolProvider toolProvider, MatchSpan[]? matchedSpans = null)
public MatchedToolProvider(ToolProviderMetadata metadata, IToolProvider toolProvider)
{
Metadata = Arguments.NotNull(metadata, nameof(metadata));
ToolProvider = Arguments.NotNull(toolProvider, nameof(toolProvider));
MatchedSpans = matchedSpans ?? Array.Empty<MatchSpan>();
MatchedSpans = Array.Empty<MatchSpan>();
}

internal async Task UpdateIsRecommendedAsync(string clipboardContent)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using DevToys.Shared.Core;

namespace DevToys.Core.Collections
Expand All @@ -23,5 +24,44 @@ internal void AddRange(IEnumerable<T> collection)

OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}

/// <summary>
/// Update the difference between the current items in the collection and the <paramref name="newItems"/>.
/// </summary>
internal void Update(IEnumerable<T> newItems)
{
// First, remove the items that aren't part of the new list items.
var oldToolsMenuItems = this.ToList();
for (int i = 0; i < oldToolsMenuItems.Count; i++)
{
T item = oldToolsMenuItems[i];
if (!newItems.Contains(item))
{
Remove(item);
}
}

// Then:
// 1. If an item from newItems already exist in the collection, but at a different position, move it to the desired index.
// 2. If an item from newItems doesn't exist in the collection, insert it with respect of the position of older items in the collection.
int insertionIndex = 0;
foreach (T? item in newItems)
{
int indexOfItemInOldMenu = IndexOf(item);
if (indexOfItemInOldMenu > -1)
{
if (indexOfItemInOldMenu != insertionIndex)
{
Move(indexOfItemInOldMenu, insertionIndex);
}
}
else
{
Insert(insertionIndex, item);
}

insertionIndex++;
}
}
}
}
47 changes: 22 additions & 25 deletions src/dev/impl/DevToys/Core/ToolProviderFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,7 @@ public async Task<IEnumerable<MatchedToolProvider>> SearchToolsAsync(string sear

return
SortTools(
SearchTools(
GetAllTools(),
searchQueries)
SearchTools(searchQueries)
.ToList());
}

Expand Down Expand Up @@ -104,38 +102,37 @@ private async void OnAppSuspending(object sender, Windows.ApplicationModel.Suspe
await CleanupAsync();
}

private IEnumerable<MatchedToolProvider> SearchTools(IEnumerable<MatchedToolProvider> providers, string[]? searchQueries)
private IEnumerable<MatchedToolProvider> SearchTools(string[]? searchQueries)
{
if (searchQueries is not null)
{
foreach (MatchedToolProvider provider in providers)
foreach (MatchedToolProvider provider in GetAllTools())
{
var matches = new List<MatchSpan>();

foreach (string? query in searchQueries)
if (provider.ChildrenTools.Count == 0) // do not search groups.
{
int i = 0;
while (i < provider.ToolProvider.DisplayName?.Length && i > -1)
var matches = new List<MatchSpan>();

foreach (string? query in searchQueries)
{
int matchIndex = provider.ToolProvider.DisplayName.IndexOf(query, i, StringComparison.OrdinalIgnoreCase);
if (matchIndex > -1)
int i = 0;
while (i < provider.ToolProvider.DisplayName?.Length && i > -1)
{
matches.Add(new MatchSpan(matchIndex, query.Length));
i = matchIndex + query.Length;
int matchIndex = provider.ToolProvider.DisplayName.IndexOf(query, i, StringComparison.OrdinalIgnoreCase);
if (matchIndex > -1)
{
matches.Add(new MatchSpan(matchIndex, query.Length));
i = matchIndex + query.Length;
}

i++;
}

i++;
}
}

if (matches.Count > 0)
{
// Return a new MatchedToolProvider with the matches.
yield return
new MatchedToolProvider(
provider.Metadata,
provider.ToolProvider,
matches.ToArray());
if (matches.Count > 0)
{
provider.MatchedSpans = matches.ToArray();
yield return provider;
}
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/dev/impl/DevToys/DevToys.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<Compile Include="Core\OOP\AppService.cs" />
<Compile Include="Core\Threading\AsyncLazy.cs" />
<Compile Include="Helpers\NumberBaseHelper.cs" />
<Compile Include="Models\NoResultFoundMockToolProvider.cs" />
<Compile Include="Models\Radix.cs" />
<Compile Include="Models\NumberBaseFormat.cs" />
<Compile Include="UI\Converters\BooleanToDoubleConverter.cs" />
Expand Down
5 changes: 5 additions & 0 deletions src/dev/impl/DevToys/LanguageManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,11 @@ public string GetFormattedNotificationReleaseNoteTitle(string? param0)
/// </summary>
public string Search => _resources.GetString("Search");

/// <summary>
/// Gets the resource SearchNoResultsFound.
/// </summary>
public string SearchNoResultsFound => _resources.GetString("SearchNoResultsFound");

/// <summary>
/// Gets the resource WindowTitle.
/// </summary>
Expand Down
30 changes: 30 additions & 0 deletions src/dev/impl/DevToys/Models/NoResultFoundMockToolProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#nullable enable

using System;
using System.ComponentModel;
using DevToys.Api.Tools;

namespace DevToys.Models
{
internal sealed class NoResultFoundMockToolProvider : IToolProvider
{
public string DisplayName => LanguageManager.Instance.MainPage.SearchNoResultsFound;

public string AccessibleName => LanguageManager.Instance.MainPage.SearchNoResultsFound;

public object IconSource => null!;

public event PropertyChangedEventHandler? PropertyChanged;

public bool CanBeTreatedByTool(string data)
{
return false;
}

public IToolViewModel CreateTool()
{
// TODO: Show a page indicating "No results match your search".
throw new NotSupportedException();
}
}
}
3 changes: 3 additions & 0 deletions src/dev/impl/DevToys/Strings/cs-CZ/MainPage.resw
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,9 @@
<data name="Search" xml:space="preserve">
<value>Hledat nástroje...</value>
</data>
<data name="SearchNoResultsFound" xml:space="preserve">
<value>No results found</value>
</data>
<data name="WindowTitle" xml:space="preserve">
<value>DevToys</value>
</data>
Expand Down
3 changes: 3 additions & 0 deletions src/dev/impl/DevToys/Strings/en-US/MainPage.resw
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,9 @@
<data name="Search" xml:space="preserve">
<value>Type to search for tools...</value>
</data>
<data name="SearchNoResultsFound" xml:space="preserve">
<value>No results found</value>
</data>
<data name="WindowTitle" xml:space="preserve">
<value>DevToys</value>
</data>
Expand Down
3 changes: 3 additions & 0 deletions src/dev/impl/DevToys/Strings/fr-FR/MainPage.resw
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,9 @@
<data name="Search" xml:space="preserve">
<value>Taper pour recherche un outil...</value>
</data>
<data name="SearchNoResultsFound" xml:space="preserve">
<value>Aucun résultat trouvé</value>
</data>
<data name="WindowTitle" xml:space="preserve">
<value>DevToys</value>
</data>
Expand Down
3 changes: 3 additions & 0 deletions src/dev/impl/DevToys/Strings/pl-PL/MainPage.resw
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,9 @@
<data name="Search" xml:space="preserve">
<value>Wpisz, aby wyszukać narzędzia...</value>
</data>
<data name="SearchNoResultsFound" xml:space="preserve">
<value>No results found</value>
</data>
<data name="WindowTitle" xml:space="preserve">
<value>DevToys</value>
</data>
Expand Down
3 changes: 3 additions & 0 deletions src/dev/impl/DevToys/Strings/ru-RU/MainPage.resw
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,9 @@
<data name="Search" xml:space="preserve">
<value>Найти инструмент</value>
</data>
<data name="SearchNoResultsFound" xml:space="preserve">
<value>No results found</value>
</data>
<data name="WindowTitle" xml:space="preserve">
<value>DevToys</value>
</data>
Expand Down
3 changes: 3 additions & 0 deletions src/dev/impl/DevToys/Strings/zh-CN/MainPage.resw
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,9 @@
<data name="Search" xml:space="preserve">
<value>搜索工具…</value>
</data>
<data name="SearchNoResultsFound" xml:space="preserve">
<value>No results found</value>
</data>
<data name="WindowTitle" xml:space="preserve">
<value>DevToys</value>
</data>
Expand Down
86 changes: 84 additions & 2 deletions src/dev/impl/DevToys/ViewModels/MainPageViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
using DevToys.Core.Settings;
using DevToys.Core.Threading;
using DevToys.Messages;
using DevToys.Models;
using DevToys.Shared.Core;
using DevToys.Shared.Core.Threading;
using Microsoft.Toolkit.Mvvm.ComponentModel;
Expand Down Expand Up @@ -120,6 +121,11 @@ internal string? SearchQuery
}
}

/// <summary>
/// Gets or sets the list of items to displayed in the Search Box after a search.
/// </summary>
internal ExtendedObservableCollection<MatchedToolProvider> SearchResults { get; } = new();

/// <summary>
/// Gets whether the window is in Compact Overlay mode or not.
/// </summary>
Expand Down Expand Up @@ -184,6 +190,8 @@ public MainPageViewModel(

OpenToolInNewWindowCommand = new AsyncRelayCommand<ToolProviderMetadata>(ExecuteOpenToolInNewWindowCommandAsync);
ChangeViewModeCommand = new AsyncRelayCommand<ApplicationViewMode>(ExecuteChangeViewModeCommandAsync);
SearchBoxTextChangedCommand = new AsyncRelayCommand<Windows.UI.Xaml.Controls.AutoSuggestBoxTextChangedEventArgs>(ExecuteSearchBoxTextChangedCommandAsync);
SearchBoxQuerySubmittedCommand = new RelayCommand<Windows.UI.Xaml.Controls.AutoSuggestBoxQuerySubmittedEventArgs>(ExecuteSearchBoxQuerySubmittedCommand);

_menuInitializationTask = BuildMenuAsync();

Expand Down Expand Up @@ -227,6 +235,80 @@ await ThreadHelper.RunOnUIThreadAsync(() =>

#endregion

#region SearchBoxTextChangedCommand

public IAsyncRelayCommand<Windows.UI.Xaml.Controls.AutoSuggestBoxTextChangedEventArgs> SearchBoxTextChangedCommand { get; }

private async Task ExecuteSearchBoxTextChangedCommandAsync(Windows.UI.Xaml.Controls.AutoSuggestBoxTextChangedEventArgs? parameters)
{
Arguments.NotNull(parameters, nameof(parameters));

await TaskScheduler.Default;

MatchedToolProvider[]? searchResult = null;

if (parameters!.Reason == Windows.UI.Xaml.Controls.AutoSuggestionBoxTextChangeReason.UserInput)
{
string? searchQuery = SearchQuery;
if (!string.IsNullOrEmpty(searchQuery))
{
IEnumerable<MatchedToolProvider> matchedTools
= await _toolProviderFactory.SearchToolsAsync(searchQuery!).ConfigureAwait(false);

if (matchedTools.Any())
{
searchResult = matchedTools.ToArray();
}
else
{
searchResult = new[]
{
new MatchedToolProvider(new ToolProviderMetadata(), new NoResultFoundMockToolProvider())
};
}
}
}

await ThreadHelper.RunOnUIThreadAsync(() =>
{
if (searchResult is null)
{
SearchResults.Clear();
}
else
{
SearchResults.Update(searchResult);
}
});
}

#endregion

#region SearchBoxQuerySubmittedCommand

public IRelayCommand<Windows.UI.Xaml.Controls.AutoSuggestBoxQuerySubmittedEventArgs> SearchBoxQuerySubmittedCommand { get; }

private void ExecuteSearchBoxQuerySubmittedCommand(Windows.UI.Xaml.Controls.AutoSuggestBoxQuerySubmittedEventArgs? parameters)
{
Arguments.NotNull(parameters, nameof(parameters));

if (string.IsNullOrEmpty(parameters!.QueryText))
{
// Nothing has been search. Do nothing.
return;
}

if (parameters.ChosenSuggestion is null or NoResultFoundMockToolProvider)
{
// TODO: Show a page indicating "No results match your search".
return;
}

SetSelectedMenuItem((MatchedToolProvider)parameters.ChosenSuggestion!, clipboardContentData: null);
}

#endregion

/// <summary>
/// Invoked when the Page is loaded and becomes the current source of a parent Frame.
/// </summary>
Expand Down Expand Up @@ -309,8 +391,8 @@ private async Task BuildMenuAsync()

try
{
IEnumerable<MatchedToolProvider> tools = await _toolProviderFactory.GetToolsTreeAsync();
IEnumerable<MatchedToolProvider> footerTools = await _toolProviderFactory.GetFooterToolsAsync();
IEnumerable<MatchedToolProvider> tools = await _toolProviderFactory.GetToolsTreeAsync().ConfigureAwait(false);
IEnumerable<MatchedToolProvider> footerTools = await _toolProviderFactory.GetFooterToolsAsync().ConfigureAwait(false);

await ThreadHelper.RunOnUIThreadAsync(
ThreadPriority.Low,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace DevToys.ViewModels.Tools
[Export(typeof(IToolProvider))]
[Name(InternalName)]
[Parent(TextGroupToolProvider.InternalName)]
[ProtocolName("text")]
[ProtocolName("formatters")]
[Order(2)]
internal sealed class FormattersGroupToolProvider : ToolProviderBase, IToolProvider
{
Expand Down
Loading

0 comments on commit 51d88a6

Please sign in to comment.