FuzzyPanel
This commit is contained in:
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\FileTime.App.Core.Abstraction\FileTime.App.Core.Abstraction.csproj" />
|
<ProjectReference Include="..\FileTime.App.Core.Abstraction\FileTime.App.Core.Abstraction.csproj" />
|
||||||
|
<ProjectReference Include="..\FileTime.App.FuzzyPanel.Abstraction\FileTime.App.FuzzyPanel.Abstraction.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
using Avalonia.Input;
|
|
||||||
using FileTime.App.Core.ViewModels;
|
using FileTime.App.Core.ViewModels;
|
||||||
|
using FileTime.App.FuzzyPanel;
|
||||||
|
|
||||||
namespace FileTime.App.FrequencyNavigation.ViewModels;
|
namespace FileTime.App.FrequencyNavigation.ViewModels;
|
||||||
|
|
||||||
public interface IFrequencyNavigationViewModel : IModalViewModel
|
public interface IFrequencyNavigationViewModel : IFuzzyPanelViewModel<string>, IModalViewModel
|
||||||
{
|
{
|
||||||
IObservable<bool> ShowWindow { get; }
|
IObservable<bool> ShowWindow { get; }
|
||||||
List<string> FilteredMatches { get; }
|
|
||||||
string SearchText { get; set; }
|
|
||||||
string SelectedItem { get; set; }
|
|
||||||
void Close();
|
void Close();
|
||||||
void HandleKeyDown(KeyEventArgs keyEventArgs);
|
|
||||||
}
|
}
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
<ProjectReference Include="..\..\Core\FileTime.Core.Abstraction\FileTime.Core.Abstraction.csproj" />
|
<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.Core.Abstraction\FileTime.App.Core.Abstraction.csproj" />
|
||||||
<ProjectReference Include="..\FileTime.App.FrequencyNavigation.Abstractions\FileTime.App.FrequencyNavigation.Abstractions.csproj" />
|
<ProjectReference Include="..\FileTime.App.FrequencyNavigation.Abstractions\FileTime.App.FrequencyNavigation.Abstractions.csproj" />
|
||||||
|
<ProjectReference Include="..\FileTime.App.FuzzyPanel\FileTime.App.FuzzyPanel.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -3,82 +3,53 @@ using FileTime.App.Core.Services;
|
|||||||
using FileTime.App.Core.UserCommand;
|
using FileTime.App.Core.UserCommand;
|
||||||
using FileTime.App.Core.ViewModels;
|
using FileTime.App.Core.ViewModels;
|
||||||
using FileTime.App.FrequencyNavigation.Services;
|
using FileTime.App.FrequencyNavigation.Services;
|
||||||
|
using FileTime.App.FuzzyPanel;
|
||||||
using FileTime.Core.Models;
|
using FileTime.Core.Models;
|
||||||
using FileTime.Core.Timeline;
|
using FileTime.Core.Timeline;
|
||||||
using MvvmGen;
|
|
||||||
|
|
||||||
namespace FileTime.App.FrequencyNavigation.ViewModels;
|
namespace FileTime.App.FrequencyNavigation.ViewModels;
|
||||||
|
|
||||||
[ViewModel]
|
public class FrequencyNavigationViewModel : FuzzyPanelViewModel<string>, IFrequencyNavigationViewModel
|
||||||
[Inject(typeof(IFrequencyNavigationService), "_frequencyNavigationService")]
|
|
||||||
[Inject(typeof(IUserCommandHandlerService), "_userCommandHandlerService")]
|
|
||||||
[Inject(typeof(ITimelessContentProvider), "_timelessContentProvider")]
|
|
||||||
public partial class FrequencyNavigationViewModel : IFrequencyNavigationViewModel
|
|
||||||
{
|
{
|
||||||
private string _searchText;
|
private readonly IFrequencyNavigationService _frequencyNavigationService;
|
||||||
|
private readonly IUserCommandHandlerService _userCommandHandlerService;
|
||||||
|
private readonly ITimelessContentProvider _timelessContentProvider;
|
||||||
|
|
||||||
[Property] private IObservable<bool> _showWindow;
|
public FrequencyNavigationViewModel(
|
||||||
[Property] private List<string> _filteredMatches;
|
IFrequencyNavigationService frequencyNavigationService,
|
||||||
[Property] private string? _selectedItem;
|
IUserCommandHandlerService userCommandHandlerService,
|
||||||
|
ITimelessContentProvider timelessContentProvider)
|
||||||
public string SearchText
|
|
||||||
{
|
{
|
||||||
get => _searchText;
|
_frequencyNavigationService = frequencyNavigationService;
|
||||||
set
|
_userCommandHandlerService = userCommandHandlerService;
|
||||||
{
|
_timelessContentProvider = timelessContentProvider;
|
||||||
if (_searchText == value) return;
|
|
||||||
|
|
||||||
_searchText = value;
|
ShowWindow = _frequencyNavigationService.ShowWindow;
|
||||||
OnPropertyChanged();
|
|
||||||
|
|
||||||
UpdateFilteredMatches();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Close()
|
public void Close()
|
||||||
=> _frequencyNavigationService.CloseNavigationWindow();
|
=> _frequencyNavigationService.CloseNavigationWindow();
|
||||||
|
|
||||||
public async void HandleKeyDown(KeyEventArgs keyEventArgs)
|
public override async Task<bool> HandleKeyDown(KeyEventArgs keyEventArgs)
|
||||||
{
|
{
|
||||||
if (keyEventArgs.Key == Key.Down)
|
var handled = await base.HandleKeyDown(keyEventArgs);
|
||||||
{
|
|
||||||
var nextItem = FilteredMatches.SkipWhile(i => i != SelectedItem).Skip(1).FirstOrDefault();
|
|
||||||
|
|
||||||
if (nextItem is not null)
|
if (handled) return true;
|
||||||
{
|
|
||||||
SelectedItem = nextItem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (keyEventArgs.Key == Key.Up)
|
|
||||||
{
|
|
||||||
var previousItem = FilteredMatches.TakeWhile(i => i != SelectedItem).LastOrDefault();
|
|
||||||
|
|
||||||
if (previousItem is not null)
|
if (keyEventArgs.Key == Key.Enter)
|
||||||
{
|
|
||||||
SelectedItem = previousItem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (keyEventArgs.Key == Key.Enter)
|
|
||||||
{
|
{
|
||||||
var targetContainer = await _timelessContentProvider.GetItemByFullNameAsync(new FullName(SelectedItem), PointInTime.Present);
|
var targetContainer = await _timelessContentProvider.GetItemByFullNameAsync(new FullName(SelectedItem), PointInTime.Present);
|
||||||
var openContainerCommand = new OpenContainerCommand(new AbsolutePath(_timelessContentProvider, targetContainer));
|
var openContainerCommand = new OpenContainerCommand(new AbsolutePath(_timelessContentProvider, targetContainer));
|
||||||
await _userCommandHandlerService.HandleCommandAsync(openContainerCommand);
|
await _userCommandHandlerService.HandleCommandAsync(openContainerCommand);
|
||||||
Close();
|
Close();
|
||||||
}
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void OnInitialize()
|
return false;
|
||||||
=> 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void UpdateFilteredMatches() =>
|
||||||
|
FilteredMatches = new List<string>(_frequencyNavigationService.GetMatchingContainers(SearchText));
|
||||||
|
|
||||||
string IModalViewModel.Name => "FrequencyNavigation";
|
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
|
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}"
|
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
|
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
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
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}.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.ActiveCfg = Release|Any CPU
|
||||||
{5B3D2008-371F-485C-92C0-127F6CD64F64}.Release|Any CPU.Build.0 = 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
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -227,6 +239,8 @@ Global
|
|||||||
{D8D4A5C3-14B5-49E7-B029-D6E5D9574388} = {A5291117-3001-498B-AC8B-E14F71F72570}
|
{D8D4A5C3-14B5-49E7-B029-D6E5D9574388} = {A5291117-3001-498B-AC8B-E14F71F72570}
|
||||||
{D0CC03DA-4705-48BD-9C4F-B11545D8BC83} = {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}
|
{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
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {859FB3DF-C60A-46B1-82E5-90274905D1EF}
|
SolutionGuid = {859FB3DF-C60A-46B1-82E5-90274905D1EF}
|
||||||
|
|||||||
Reference in New Issue
Block a user