FuzzyPanel

This commit is contained in:
2023-06-30 14:01:25 +02:00
parent 0ed161c819
commit b5d332b3bd
9 changed files with 155 additions and 57 deletions

View File

@@ -9,6 +9,7 @@
<ItemGroup>
<ProjectReference Include="..\FileTime.App.Core.Abstraction\FileTime.App.Core.Abstraction.csproj" />
<ProjectReference Include="..\FileTime.App.FuzzyPanel.Abstraction\FileTime.App.FuzzyPanel.Abstraction.csproj" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,14 +1,10 @@
using Avalonia.Input;
using FileTime.App.Core.ViewModels;
using FileTime.App.FuzzyPanel;
namespace FileTime.App.FrequencyNavigation.ViewModels;
public interface IFrequencyNavigationViewModel : IModalViewModel
public interface IFrequencyNavigationViewModel : IFuzzyPanelViewModel<string>, IModalViewModel
{
IObservable<bool> ShowWindow { get; }
List<string> FilteredMatches { get; }
string SearchText { get; set; }
string SelectedItem { get; set; }
void Close();
void HandleKeyDown(KeyEventArgs keyEventArgs);
}

View File

@@ -10,6 +10,7 @@
<ProjectReference Include="..\..\Core\FileTime.Core.Abstraction\FileTime.Core.Abstraction.csproj" />
<ProjectReference Include="..\FileTime.App.Core.Abstraction\FileTime.App.Core.Abstraction.csproj" />
<ProjectReference Include="..\FileTime.App.FrequencyNavigation.Abstractions\FileTime.App.FrequencyNavigation.Abstractions.csproj" />
<ProjectReference Include="..\FileTime.App.FuzzyPanel\FileTime.App.FuzzyPanel.csproj" />
</ItemGroup>
<ItemGroup>

View File

@@ -3,82 +3,53 @@ using FileTime.App.Core.Services;
using FileTime.App.Core.UserCommand;
using FileTime.App.Core.ViewModels;
using FileTime.App.FrequencyNavigation.Services;
using FileTime.App.FuzzyPanel;
using FileTime.Core.Models;
using FileTime.Core.Timeline;
using MvvmGen;
namespace FileTime.App.FrequencyNavigation.ViewModels;
[ViewModel]
[Inject(typeof(IFrequencyNavigationService), "_frequencyNavigationService")]
[Inject(typeof(IUserCommandHandlerService), "_userCommandHandlerService")]
[Inject(typeof(ITimelessContentProvider), "_timelessContentProvider")]
public partial class FrequencyNavigationViewModel : IFrequencyNavigationViewModel
public class FrequencyNavigationViewModel : FuzzyPanelViewModel<string>, IFrequencyNavigationViewModel
{
private string _searchText;
private readonly IFrequencyNavigationService _frequencyNavigationService;
private readonly IUserCommandHandlerService _userCommandHandlerService;
private readonly ITimelessContentProvider _timelessContentProvider;
[Property] private IObservable<bool> _showWindow;
[Property] private List<string> _filteredMatches;
[Property] private string? _selectedItem;
public string SearchText
public FrequencyNavigationViewModel(
IFrequencyNavigationService frequencyNavigationService,
IUserCommandHandlerService userCommandHandlerService,
ITimelessContentProvider timelessContentProvider)
{
get => _searchText;
set
{
if (_searchText == value) return;
_frequencyNavigationService = frequencyNavigationService;
_userCommandHandlerService = userCommandHandlerService;
_timelessContentProvider = timelessContentProvider;
_searchText = value;
OnPropertyChanged();
UpdateFilteredMatches();
}
ShowWindow = _frequencyNavigationService.ShowWindow;
}
public void Close()
=> _frequencyNavigationService.CloseNavigationWindow();
public async void HandleKeyDown(KeyEventArgs keyEventArgs)
public override async Task<bool> HandleKeyDown(KeyEventArgs keyEventArgs)
{
if (keyEventArgs.Key == Key.Down)
{
var nextItem = FilteredMatches.SkipWhile(i => i != SelectedItem).Skip(1).FirstOrDefault();
var handled = await base.HandleKeyDown(keyEventArgs);
if (nextItem is not null)
{
SelectedItem = nextItem;
}
}
else if (keyEventArgs.Key == Key.Up)
{
var previousItem = FilteredMatches.TakeWhile(i => i != SelectedItem).LastOrDefault();
if (handled) return true;
if (previousItem is not null)
{
SelectedItem = previousItem;
}
}
else if (keyEventArgs.Key == Key.Enter)
if (keyEventArgs.Key == Key.Enter)
{
var targetContainer = await _timelessContentProvider.GetItemByFullNameAsync(new FullName(SelectedItem), PointInTime.Present);
var openContainerCommand = new OpenContainerCommand(new AbsolutePath(_timelessContentProvider, targetContainer));
await _userCommandHandlerService.HandleCommandAsync(openContainerCommand);
Close();
}
return true;
}
partial void OnInitialize()
=> ShowWindow = _frequencyNavigationService.ShowWindow;
private void UpdateFilteredMatches()
{
FilteredMatches = new List<string>(_frequencyNavigationService.GetMatchingContainers(_searchText));
if (SelectedItem != null && FilteredMatches.Contains(SelectedItem)) return;
SelectedItem = FilteredMatches.Count > 0
? FilteredMatches[0]
: null;
return false;
}
public override void UpdateFilteredMatches() =>
FilteredMatches = new List<string>(_frequencyNavigationService.GetMatchingContainers(SearchText));
string IModalViewModel.Name => "FrequencyNavigation";
}

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>FileTime.App.FuzzyPanel</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.0.0-preview8" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,12 @@
using Avalonia.Input;
namespace FileTime.App.FuzzyPanel;
public interface IFuzzyPanelViewModel<TItem> where TItem : class
{
List<TItem> FilteredMatches { get; }
TItem? SelectedItem { get; }
string SearchText { get; set; }
void UpdateFilteredMatches();
Task<bool> HandleKeyDown(KeyEventArgs keyEventArgs);
}

View File

@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.0.0-preview8" />
<PackageReference Include="PropertyChanged.SourceGenerator" Version="1.0.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FileTime.App.FuzzyPanel.Abstraction\FileTime.App.FuzzyPanel.Abstraction.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,68 @@
using System.ComponentModel;
using Avalonia.Input;
using PropertyChanged.SourceGenerator;
namespace FileTime.App.FuzzyPanel;
public abstract partial class FuzzyPanelViewModel<TItem> : IFuzzyPanelViewModel<TItem> where TItem : class
{
private string _searchText = String.Empty;
[Notify(set: Setter.Protected)] private IObservable<bool> _showWindow;
[Notify(set: Setter.Protected)] private List<TItem> _filteredMatches;
[Notify(set: Setter.Protected)] private TItem? _selectedItem;
public string SearchText
{
get => _searchText;
set
{
if (_searchText == value) return;
_searchText = value;
OnPropertyChanged(new PropertyChangedEventArgs(nameof(SearchText)));
UpdateFilteredMatchesInternal();
}
}
private void UpdateFilteredMatchesInternal()
{
UpdateFilteredMatches();
if (SelectedItem != null && FilteredMatches.Contains(SelectedItem)) return;
SelectedItem = FilteredMatches.Count > 0
? FilteredMatches[0]
: null;
}
public abstract void UpdateFilteredMatches();
public virtual Task<bool> HandleKeyDown(KeyEventArgs keyEventArgs)
{
if (keyEventArgs.Key == Key.Down)
{
var nextItem = FilteredMatches.SkipWhile(i => i != SelectedItem).Skip(1).FirstOrDefault();
if (nextItem is not null)
{
SelectedItem = nextItem;
}
return Task.FromResult(true);
}
else if (keyEventArgs.Key == Key.Up)
{
var previousItem = FilteredMatches.TakeWhile(i => i != SelectedItem).LastOrDefault();
if (previousItem is not null)
{
SelectedItem = previousItem;
}
return Task.FromResult(true);
}
return Task.FromResult(false);
}
}

View File

@@ -75,6 +75,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.App.CommandPalette
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.App.CommandPalette.Abstractions", "AppCommon\FileTime.App.CommandPalette.Abstractions\FileTime.App.CommandPalette.Abstractions.csproj", "{5B3D2008-371F-485C-92C0-127F6CD64F64}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.App.FuzzyPanel", "AppCommon\FileTime.App.FuzzyPanel\FileTime.App.FuzzyPanel.csproj", "{7FDCE43D-D084-4539-B797-8A72D4DD610D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.App.FuzzyPanel.Abstraction", "AppCommon\FileTime.App.FuzzyPanel.Abstraction\FileTime.App.FuzzyPanel.Abstraction.csproj", "{7690E4EA-DA2A-45B4-AD83-80EE07A05169}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -193,6 +197,14 @@ Global
{5B3D2008-371F-485C-92C0-127F6CD64F64}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5B3D2008-371F-485C-92C0-127F6CD64F64}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5B3D2008-371F-485C-92C0-127F6CD64F64}.Release|Any CPU.Build.0 = Release|Any CPU
{7FDCE43D-D084-4539-B797-8A72D4DD610D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7FDCE43D-D084-4539-B797-8A72D4DD610D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7FDCE43D-D084-4539-B797-8A72D4DD610D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7FDCE43D-D084-4539-B797-8A72D4DD610D}.Release|Any CPU.Build.0 = Release|Any CPU
{7690E4EA-DA2A-45B4-AD83-80EE07A05169}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7690E4EA-DA2A-45B4-AD83-80EE07A05169}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7690E4EA-DA2A-45B4-AD83-80EE07A05169}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7690E4EA-DA2A-45B4-AD83-80EE07A05169}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -227,6 +239,8 @@ Global
{D8D4A5C3-14B5-49E7-B029-D6E5D9574388} = {A5291117-3001-498B-AC8B-E14F71F72570}
{D0CC03DA-4705-48BD-9C4F-B11545D8BC83} = {A5291117-3001-498B-AC8B-E14F71F72570}
{5B3D2008-371F-485C-92C0-127F6CD64F64} = {A5291117-3001-498B-AC8B-E14F71F72570}
{7FDCE43D-D084-4539-B797-8A72D4DD610D} = {A5291117-3001-498B-AC8B-E14F71F72570}
{7690E4EA-DA2A-45B4-AD83-80EE07A05169} = {A5291117-3001-498B-AC8B-E14F71F72570}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {859FB3DF-C60A-46B1-82E5-90274905D1EF}