diff --git a/src/AppCommon/FileTime.App.FrequencyNavigation.Abstractions/FileTime.App.FrequencyNavigation.Abstractions.csproj b/src/AppCommon/FileTime.App.FrequencyNavigation.Abstractions/FileTime.App.FrequencyNavigation.Abstractions.csproj index 108d523..8314411 100644 --- a/src/AppCommon/FileTime.App.FrequencyNavigation.Abstractions/FileTime.App.FrequencyNavigation.Abstractions.csproj +++ b/src/AppCommon/FileTime.App.FrequencyNavigation.Abstractions/FileTime.App.FrequencyNavigation.Abstractions.csproj @@ -9,6 +9,7 @@ + diff --git a/src/AppCommon/FileTime.App.FrequencyNavigation.Abstractions/ViewModels/IFrequencyNavigationViewModel.cs b/src/AppCommon/FileTime.App.FrequencyNavigation.Abstractions/ViewModels/IFrequencyNavigationViewModel.cs index 7fd7a3f..fcc5878 100644 --- a/src/AppCommon/FileTime.App.FrequencyNavigation.Abstractions/ViewModels/IFrequencyNavigationViewModel.cs +++ b/src/AppCommon/FileTime.App.FrequencyNavigation.Abstractions/ViewModels/IFrequencyNavigationViewModel.cs @@ -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, IModalViewModel { IObservable ShowWindow { get; } - List FilteredMatches { get; } - string SearchText { get; set; } - string SelectedItem { get; set; } void Close(); - void HandleKeyDown(KeyEventArgs keyEventArgs); } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.FrequencyNavigation/FileTime.App.FrequencyNavigation.csproj b/src/AppCommon/FileTime.App.FrequencyNavigation/FileTime.App.FrequencyNavigation.csproj index e6fddd6..ad9f25c 100644 --- a/src/AppCommon/FileTime.App.FrequencyNavigation/FileTime.App.FrequencyNavigation.csproj +++ b/src/AppCommon/FileTime.App.FrequencyNavigation/FileTime.App.FrequencyNavigation.csproj @@ -10,6 +10,7 @@ + diff --git a/src/AppCommon/FileTime.App.FrequencyNavigation/ViewModels/FrequencyNavigationViewModel.cs b/src/AppCommon/FileTime.App.FrequencyNavigation/ViewModels/FrequencyNavigationViewModel.cs index 14fd07d..b40986b 100644 --- a/src/AppCommon/FileTime.App.FrequencyNavigation/ViewModels/FrequencyNavigationViewModel.cs +++ b/src/AppCommon/FileTime.App.FrequencyNavigation/ViewModels/FrequencyNavigationViewModel.cs @@ -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, IFrequencyNavigationViewModel { - private string _searchText; + private readonly IFrequencyNavigationService _frequencyNavigationService; + private readonly IUserCommandHandlerService _userCommandHandlerService; + private readonly ITimelessContentProvider _timelessContentProvider; - [Property] private IObservable _showWindow; - [Property] private List _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 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; } + + return false; } - partial void OnInitialize() - => ShowWindow = _frequencyNavigationService.ShowWindow; - - private void UpdateFilteredMatches() - { - FilteredMatches = new List(_frequencyNavigationService.GetMatchingContainers(_searchText)); - if (SelectedItem != null && FilteredMatches.Contains(SelectedItem)) return; - - SelectedItem = FilteredMatches.Count > 0 - ? FilteredMatches[0] - : null; - } + public override void UpdateFilteredMatches() => + FilteredMatches = new List(_frequencyNavigationService.GetMatchingContainers(SearchText)); string IModalViewModel.Name => "FrequencyNavigation"; } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.FuzzyPanel.Abstraction/FileTime.App.FuzzyPanel.Abstraction.csproj b/src/AppCommon/FileTime.App.FuzzyPanel.Abstraction/FileTime.App.FuzzyPanel.Abstraction.csproj new file mode 100644 index 0000000..f2f54a5 --- /dev/null +++ b/src/AppCommon/FileTime.App.FuzzyPanel.Abstraction/FileTime.App.FuzzyPanel.Abstraction.csproj @@ -0,0 +1,14 @@ + + + + net7.0 + enable + enable + FileTime.App.FuzzyPanel + + + + + + + diff --git a/src/AppCommon/FileTime.App.FuzzyPanel.Abstraction/IFuzzyPanelViewModel.cs b/src/AppCommon/FileTime.App.FuzzyPanel.Abstraction/IFuzzyPanelViewModel.cs new file mode 100644 index 0000000..dc968fd --- /dev/null +++ b/src/AppCommon/FileTime.App.FuzzyPanel.Abstraction/IFuzzyPanelViewModel.cs @@ -0,0 +1,12 @@ +using Avalonia.Input; + +namespace FileTime.App.FuzzyPanel; + +public interface IFuzzyPanelViewModel where TItem : class +{ + List FilteredMatches { get; } + TItem? SelectedItem { get; } + string SearchText { get; set; } + void UpdateFilteredMatches(); + Task HandleKeyDown(KeyEventArgs keyEventArgs); +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.FuzzyPanel/FileTime.App.FuzzyPanel.csproj b/src/AppCommon/FileTime.App.FuzzyPanel/FileTime.App.FuzzyPanel.csproj new file mode 100644 index 0000000..04448d7 --- /dev/null +++ b/src/AppCommon/FileTime.App.FuzzyPanel/FileTime.App.FuzzyPanel.csproj @@ -0,0 +1,21 @@ + + + + net7.0 + enable + enable + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/src/AppCommon/FileTime.App.FuzzyPanel/FuzzyPanelViewModel.cs b/src/AppCommon/FileTime.App.FuzzyPanel/FuzzyPanelViewModel.cs new file mode 100644 index 0000000..74ca308 --- /dev/null +++ b/src/AppCommon/FileTime.App.FuzzyPanel/FuzzyPanelViewModel.cs @@ -0,0 +1,68 @@ +using System.ComponentModel; +using Avalonia.Input; +using PropertyChanged.SourceGenerator; + +namespace FileTime.App.FuzzyPanel; + +public abstract partial class FuzzyPanelViewModel : IFuzzyPanelViewModel where TItem : class +{ + private string _searchText = String.Empty; + + [Notify(set: Setter.Protected)] private IObservable _showWindow; + [Notify(set: Setter.Protected)] private List _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 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); + } +} \ No newline at end of file diff --git a/src/FileTime.sln b/src/FileTime.sln index 6d28bdd..4bc0bb2 100644 --- a/src/FileTime.sln +++ b/src/FileTime.sln @@ -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}