diff --git a/src/AppCommon/FileTime.App.CommandPalette.Abstractions/FileTime.App.CommandPalette.Abstractions.csproj b/src/AppCommon/FileTime.App.CommandPalette.Abstractions/FileTime.App.CommandPalette.Abstractions.csproj
index 2da1ed9..0433a7a 100644
--- a/src/AppCommon/FileTime.App.CommandPalette.Abstractions/FileTime.App.CommandPalette.Abstractions.csproj
+++ b/src/AppCommon/FileTime.App.CommandPalette.Abstractions/FileTime.App.CommandPalette.Abstractions.csproj
@@ -9,6 +9,7 @@
+
diff --git a/src/AppCommon/FileTime.App.CommandPalette.Abstractions/Services/ICommandPaletteService.cs b/src/AppCommon/FileTime.App.CommandPalette.Abstractions/Services/ICommandPaletteService.cs
index 861f4a6..b1d476f 100644
--- a/src/AppCommon/FileTime.App.CommandPalette.Abstractions/Services/ICommandPaletteService.cs
+++ b/src/AppCommon/FileTime.App.CommandPalette.Abstractions/Services/ICommandPaletteService.cs
@@ -7,6 +7,7 @@ public interface ICommandPaletteService
{
IObservable ShowWindow { get; }
void OpenCommandPalette();
+ void CloseCommandPalette();
IReadOnlyList GetCommands();
ICommandPaletteViewModel? CurrentModal { get; }
}
\ No newline at end of file
diff --git a/src/AppCommon/FileTime.App.CommandPalette.Abstractions/ViewModels/ICommandPaletteViewModel.cs b/src/AppCommon/FileTime.App.CommandPalette.Abstractions/ViewModels/ICommandPaletteViewModel.cs
index 3e8b3f0..738732d 100644
--- a/src/AppCommon/FileTime.App.CommandPalette.Abstractions/ViewModels/ICommandPaletteViewModel.cs
+++ b/src/AppCommon/FileTime.App.CommandPalette.Abstractions/ViewModels/ICommandPaletteViewModel.cs
@@ -1,14 +1,10 @@
-using Avalonia.Input;
-using FileTime.App.Core.ViewModels;
+using FileTime.App.Core.ViewModels;
+using FileTime.App.FuzzyPanel;
namespace FileTime.App.CommandPalette.ViewModels;
-public interface ICommandPaletteViewModel : IModalViewModel
+public interface ICommandPaletteViewModel : IFuzzyPanelViewModel, IModalViewModel
{
IObservable ShowWindow { get; }
- List FilteredMatches { get; }
- string SearchText { get; set; }
- ICommandPaletteEntryViewModel SelectedItem { get; set; }
void Close();
- void HandleKeyDown(KeyEventArgs keyEventArgs);
}
\ No newline at end of file
diff --git a/src/AppCommon/FileTime.App.CommandPalette/FileTime.App.CommandPalette.csproj b/src/AppCommon/FileTime.App.CommandPalette/FileTime.App.CommandPalette.csproj
index 36e5f38..4a0a6a7 100644
--- a/src/AppCommon/FileTime.App.CommandPalette/FileTime.App.CommandPalette.csproj
+++ b/src/AppCommon/FileTime.App.CommandPalette/FileTime.App.CommandPalette.csproj
@@ -9,10 +9,12 @@
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/src/AppCommon/FileTime.App.CommandPalette/Services/CommandPaletteService.cs b/src/AppCommon/FileTime.App.CommandPalette/Services/CommandPaletteService.cs
index 36995b5..c6a09a4 100644
--- a/src/AppCommon/FileTime.App.CommandPalette/Services/CommandPaletteService.cs
+++ b/src/AppCommon/FileTime.App.CommandPalette/Services/CommandPaletteService.cs
@@ -28,6 +28,16 @@ public partial class CommandPaletteService : ICommandPaletteService
CurrentModal = _modalService.OpenModal();
}
+ public void CloseCommandPalette()
+ {
+ _showWindow.OnNext(false);
+ if (_currentModal is not null)
+ {
+ _modalService.CloseModal(_currentModal);
+ CurrentModal = null;
+ }
+ }
+
public IReadOnlyList GetCommands() =>
_identifiableUserCommandService
.GetCommandIdentifiers()
diff --git a/src/AppCommon/FileTime.App.CommandPalette/ViewModels/CommandPaletteViewModel.cs b/src/AppCommon/FileTime.App.CommandPalette/ViewModels/CommandPaletteViewModel.cs
index 079d53e..967dce4 100644
--- a/src/AppCommon/FileTime.App.CommandPalette/ViewModels/CommandPaletteViewModel.cs
+++ b/src/AppCommon/FileTime.App.CommandPalette/ViewModels/CommandPaletteViewModel.cs
@@ -1,59 +1,85 @@
using Avalonia.Input;
using FileTime.App.CommandPalette.Services;
+using FileTime.App.Core.Services;
using FileTime.App.Core.ViewModels;
-using MvvmGen;
+using FileTime.App.FuzzyPanel;
+using Microsoft.Extensions.Logging;
namespace FileTime.App.CommandPalette.ViewModels;
-[ViewModel]
-[Inject(typeof(ICommandPaletteService), "_commandPaletteService")]
-public partial class CommandPaletteViewModel : ICommandPaletteViewModel
+public class CommandPaletteViewModel : FuzzyPanelViewModel, ICommandPaletteViewModel
{
- private string _searchText;
-
- [Property] private IObservable _showWindow;
- [Property] private List _filteredMatches;
- [Property] private ICommandPaletteEntryViewModel? _selectedItem;
+ private readonly ICommandPaletteService _commandPaletteService;
+ private readonly IIdentifiableUserCommandService _identifiableUserCommandService;
+ private readonly IUserCommandHandlerService _userCommandHandlerService;
+ private readonly ILogger _logger;
string IModalViewModel.Name => "CommandPalette";
- public string SearchText
- {
- get => _searchText;
- set
- {
- if (_searchText == value) return;
-
- _searchText = value;
- OnPropertyChanged();
-
- UpdateFilteredMatches();
- }
- }
-
- public void Close() => throw new NotImplementedException();
-
- public void HandleKeyDown(KeyEventArgs keyEventArgs) => throw new NotImplementedException();
-
- partial void OnInitialize()
+ public CommandPaletteViewModel(
+ ICommandPaletteService commandPaletteService,
+ IIdentifiableUserCommandService identifiableUserCommandService,
+ IUserCommandHandlerService userCommandHandlerService,
+ ILogger logger)
{
+ _commandPaletteService = commandPaletteService;
+ _identifiableUserCommandService = identifiableUserCommandService;
+ _userCommandHandlerService = userCommandHandlerService;
+ _logger = logger;
ShowWindow = _commandPaletteService.ShowWindow;
- UpdateFilteredMatches();
+ UpdateFilteredMatchesInternal();
}
- private void UpdateFilteredMatches()
- {
+ public void Close() => _commandPaletteService.CloseCommandPalette();
+
+ public override void UpdateFilteredMatches() => UpdateFilteredMatchesInternal();
+
+ private void UpdateFilteredMatchesInternal() =>
FilteredMatches = _commandPaletteService
.GetCommands()
+ .Where(c =>
+ c.Title.Contains(SearchText, StringComparison.OrdinalIgnoreCase)
+ || c.Identifier.Contains(SearchText, StringComparison.OrdinalIgnoreCase)
+ )
.Select(c =>
(ICommandPaletteEntryViewModel) new CommandPaletteEntryViewModel(c.Identifier, c.Title))
.Take(30) // TODO remove magic number
.OrderBy(c => c.Title)
.ToList();
- if (SelectedItem != null && FilteredMatches.Contains(SelectedItem)) return;
+ public override async Task HandleKeyDown(KeyEventArgs keyEventArgs)
+ {
+ var handled = await base.HandleKeyDown(keyEventArgs);
+ if (handled)
+ {
+ return true;
+ }
- SelectedItem = FilteredMatches.Count > 0
- ? FilteredMatches[0]
- : null;
+ if (keyEventArgs.Key == Key.Escape)
+ {
+ Close();
+ return true;
+ }
+
+ if (keyEventArgs.Key == Key.Enter)
+ {
+ if (SelectedItem is null) return false;
+
+ var command = _identifiableUserCommandService.GetCommand(SelectedItem.Identifier);
+ if (command is null) return false;
+
+ try
+ {
+ await _userCommandHandlerService.HandleCommandAsync(command);
+ }
+ catch (Exception e)
+ {
+ _logger.LogError(e, "Unknown error while running command. {Command} {Error}", command.GetType().Name, e);
+ }
+
+ Close();
+ return true;
+ }
+
+ return false;
}
}
\ No newline at end of file
diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/Services/IIdentifiableUserCommandService.cs b/src/AppCommon/FileTime.App.Core.Abstraction/Services/IIdentifiableUserCommandService.cs
index 48d53c5..c925895 100644
--- a/src/AppCommon/FileTime.App.Core.Abstraction/Services/IIdentifiableUserCommandService.cs
+++ b/src/AppCommon/FileTime.App.Core.Abstraction/Services/IIdentifiableUserCommandService.cs
@@ -5,6 +5,6 @@ namespace FileTime.App.Core.Services;
public interface IIdentifiableUserCommandService
{
void AddIdentifiableUserCommandFactory(string identifier, Func commandFactory);
- IIdentifiableUserCommand GetCommand(string identifier);
+ IIdentifiableUserCommand? GetCommand(string identifier);
IReadOnlyCollection GetCommandIdentifiers();
}
\ No newline at end of file
diff --git a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/IdentifiableUserCommandService.cs b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/IdentifiableUserCommandService.cs
index aa37ac7..9d65eb0 100644
--- a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/IdentifiableUserCommandService.cs
+++ b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/IdentifiableUserCommandService.cs
@@ -9,8 +9,9 @@ public class IdentifiableUserCommandService : IIdentifiableUserCommandService
public void AddIdentifiableUserCommandFactory(string identifier, Func commandFactory)
=> _identifiableUserCommands.Add(identifier, commandFactory);
- public IIdentifiableUserCommand GetCommand(string identifier)
+ public IIdentifiableUserCommand? GetCommand(string identifier)
{
+ //TODO: refactor to not throw an exception
if (!_identifiableUserCommands.ContainsKey(identifier))
throw new IndexOutOfRangeException($"No command factory is registered for command {identifier}");
diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/CommandPalette.axaml b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/CommandPalette.axaml
index b6098e9..47e3916 100644
--- a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/CommandPalette.axaml
+++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/CommandPalette.axaml
@@ -12,9 +12,9 @@
+ Text="{Binding SearchText, Mode=TwoWay}"
+ x:Name="SearchTextBox" />
-
+
\ No newline at end of file
diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/CommandPalette.axaml.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/CommandPalette.axaml.cs
index 1b849e2..2a809d8 100644
--- a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/CommandPalette.axaml.cs
+++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/CommandPalette.axaml.cs
@@ -1,7 +1,9 @@
-using Avalonia.Controls;
+using Avalonia;
+using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Markup.Xaml;
-using FileTime.App.FrequencyNavigation.ViewModels;
+using Avalonia.Threading;
+using FileTime.App.CommandPalette.ViewModels;
namespace FileTime.GuiApp.Views;
@@ -10,17 +12,22 @@ public partial class CommandPalette : UserControl
public CommandPalette()
{
InitializeComponent();
+ PropertyChanged += CommandPalette_PropertyChanged;
}
- private void InitializeComponent()
+ private async void CommandPalette_PropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
- AvaloniaXamlLoader.Load(this);
+ if (e.Property.Name == nameof(IsVisible) && IsVisible)
+ {
+ await Task.Delay(10);
+ SearchTextBox.Focus();
+ }
}
private void Search_OnKeyDown(object? sender, KeyEventArgs e)
{
- if (DataContext is not IFrequencyNavigationViewModel viewModel) return;
-
+ if (DataContext is not ICommandPaletteViewModel viewModel) return;
+
if (e.Key == Key.Escape)
{
viewModel.Close();
@@ -30,4 +37,9 @@ public partial class CommandPalette : UserControl
viewModel.HandleKeyDown(e);
}
}
+
+ public void Test()
+ {
+ ;
+ }
}
\ No newline at end of file
diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/FrequencyNavigation.axaml b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/FrequencyNavigation.axaml
index 962746e..16297d9 100644
--- a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/FrequencyNavigation.axaml
+++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/FrequencyNavigation.axaml
@@ -1,27 +1,27 @@
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+ Text="{Binding SearchText, Mode=TwoWay}"
+ x:Name="SearchTextBox" />
diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/FrequencyNavigation.axaml.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/FrequencyNavigation.axaml.cs
index de21d8e..f1e3b7e 100644
--- a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/FrequencyNavigation.axaml.cs
+++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/FrequencyNavigation.axaml.cs
@@ -1,6 +1,6 @@
+using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
-using Avalonia.Markup.Xaml;
using FileTime.App.FrequencyNavigation.ViewModels;
namespace FileTime.GuiApp.Views;
@@ -10,11 +10,16 @@ public partial class FrequencyNavigation : UserControl
public FrequencyNavigation()
{
InitializeComponent();
+ PropertyChanged += FrequencyNavigation_PropertyChanged;
}
- private void InitializeComponent()
+ private async void FrequencyNavigation_PropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
- AvaloniaXamlLoader.Load(this);
+ if (e.Property.Name == nameof(IsVisible) && IsVisible)
+ {
+ await Task.Delay(100);
+ SearchTextBox.Focus();
+ }
}
private void Search_OnKeyDown(object? sender, KeyEventArgs e)
diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml
index f714c93..1c0142d 100644
--- a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml
+++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml
@@ -672,7 +672,7 @@
IsVisible="{Binding ShowWindow^, FallbackValue=False}"
VerticalAlignment="Stretch">
-
+
@@ -683,7 +683,7 @@
IsVisible="{Binding ShowWindow^, FallbackValue=False}"
VerticalAlignment="Stretch">
-
+