FuzzyPanel
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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>
|
||||
68
src/AppCommon/FileTime.App.FuzzyPanel/FuzzyPanelViewModel.cs
Normal file
68
src/AppCommon/FileTime.App.FuzzyPanel/FuzzyPanelViewModel.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user