CommandPalette WIP

This commit is contained in:
2023-05-22 09:29:41 +02:00
parent 2fe362ad6e
commit 0ed161c819
59 changed files with 536 additions and 276 deletions

View File

@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>FileTime.App.CommandPalette</RootNamespace>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\FileTime.App.Core.Abstraction\FileTime.App.Core.Abstraction.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.0.0-preview8" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,8 @@
namespace FileTime.App.CommandPalette.Models;
public interface ICommandPaletteEntry
{
string Identifier { get; }
string Title { get; }
}

View File

@@ -0,0 +1,12 @@
using FileTime.App.CommandPalette.Models;
using FileTime.App.CommandPalette.ViewModels;
namespace FileTime.App.CommandPalette.Services;
public interface ICommandPaletteService
{
IObservable<bool> ShowWindow { get; }
void OpenCommandPalette();
IReadOnlyList<ICommandPaletteEntry> GetCommands();
ICommandPaletteViewModel? CurrentModal { get; }
}

View File

@@ -0,0 +1,7 @@
namespace FileTime.App.CommandPalette.ViewModels;
public interface ICommandPaletteEntryViewModel
{
string Identifier { get; set; }
string Title { get; set; }
}

View File

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

View File

@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\FileTime.App.CommandPalette.Abstractions\FileTime.App.CommandPalette.Abstractions.csproj" />
<ProjectReference Include="..\FileTime.App.Core.Abstraction\FileTime.App.Core.Abstraction.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
<PackageReference Include="PropertyChanged.SourceGenerator" Version="1.0.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Reactive" Version="6.0.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,13 @@
namespace FileTime.App.CommandPalette.Models;
public class CommandPaletteEntry : ICommandPaletteEntry
{
public string Identifier { get; }
public string Title { get; }
public CommandPaletteEntry(string identifier, string title)
{
Identifier = identifier;
Title = title;
}
}

View File

@@ -0,0 +1,37 @@
using System.Reactive.Linq;
using System.Reactive.Subjects;
using FileTime.App.CommandPalette.Models;
using FileTime.App.CommandPalette.ViewModels;
using FileTime.App.Core.Services;
using PropertyChanged.SourceGenerator;
namespace FileTime.App.CommandPalette.Services;
public partial class CommandPaletteService : ICommandPaletteService
{
private readonly IModalService _modalService;
private readonly IIdentifiableUserCommandService _identifiableUserCommandService;
private readonly BehaviorSubject<bool> _showWindow = new(false);
IObservable<bool> ICommandPaletteService.ShowWindow => _showWindow.AsObservable();
[Notify] ICommandPaletteViewModel? _currentModal;
public CommandPaletteService(
IModalService modalService,
IIdentifiableUserCommandService identifiableUserCommandService)
{
_modalService = modalService;
_identifiableUserCommandService = identifiableUserCommandService;
}
public void OpenCommandPalette()
{
_showWindow.OnNext(true);
CurrentModal = _modalService.OpenModal<ICommandPaletteViewModel>();
}
public IReadOnlyList<ICommandPaletteEntry> GetCommands() =>
_identifiableUserCommandService
.GetCommandIdentifiers()
.Select(c => new CommandPaletteEntry(c, c))
.ToList()
.AsReadOnly();
}

View File

@@ -0,0 +1,16 @@
using FileTime.App.CommandPalette.Services;
using FileTime.App.CommandPalette.ViewModels;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace FileTime.App.CommandPalette;
public static class Startup
{
public static IServiceCollection AddCommandPalette(this IServiceCollection services)
{
services.TryAddTransient<ICommandPaletteViewModel, CommandPaletteViewModel>();
services.TryAddSingleton<ICommandPaletteService, CommandPaletteService>();
return services;
}
}

View File

@@ -0,0 +1,16 @@
using MvvmGen;
namespace FileTime.App.CommandPalette.ViewModels;
[ViewModel]
public partial class CommandPaletteEntryViewModel : ICommandPaletteEntryViewModel
{
[Property] private string _identifier;
[Property] private string _title;
public CommandPaletteEntryViewModel(string identifier, string title)
{
_identifier = identifier;
_title = title;
}
}

View File

@@ -0,0 +1,59 @@
using Avalonia.Input;
using FileTime.App.CommandPalette.Services;
using FileTime.App.Core.ViewModels;
using MvvmGen;
namespace FileTime.App.CommandPalette.ViewModels;
[ViewModel]
[Inject(typeof(ICommandPaletteService), "_commandPaletteService")]
public partial class CommandPaletteViewModel : ICommandPaletteViewModel
{
private string _searchText;
[Property] private IObservable<bool> _showWindow;
[Property] private List<ICommandPaletteEntryViewModel> _filteredMatches;
[Property] private ICommandPaletteEntryViewModel? _selectedItem;
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()
{
ShowWindow = _commandPaletteService.ShowWindow;
UpdateFilteredMatches();
}
private void UpdateFilteredMatches()
{
FilteredMatches = _commandPaletteService
.GetCommands()
.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;
SelectedItem = FilteredMatches.Count > 0
? FilteredMatches[0]
: null;
}
}

View File

@@ -1,5 +1,3 @@
using System.Collections.ObjectModel;
namespace FileTime.App.Core.Extensions;
public static class DisposableExtensions

View File

@@ -6,4 +6,5 @@ public interface IIdentifiableUserCommandService
{
void AddIdentifiableUserCommandFactory(string identifier, Func<IIdentifiableUserCommand> commandFactory);
IIdentifiableUserCommand GetCommand(string identifier);
IReadOnlyCollection<string> GetCommandIdentifiers();
}

View File

@@ -3,4 +3,5 @@ namespace FileTime.App.Core.UserCommand;
public interface IIdentifiableUserCommand : IUserCommand
{
string UserCommandID { get; }
//string Title { get; }
}

View File

@@ -0,0 +1,13 @@
namespace FileTime.App.Core.UserCommand;
public class OpenCommandPaletteCommand : IIdentifiableUserCommand
{
public const string CommandName = "open_command_palette";
public static OpenCommandPaletteCommand Instance { get; } = new ();
private OpenCommandPaletteCommand()
{
}
public string UserCommandID => CommandName;
}

View File

@@ -1,6 +1,5 @@
using DynamicData;
using FileTime.App.Core.Models;
using FileTime.Core.Models;
using FileTime.Core.Services;
using InitableService;

View File

@@ -1,5 +1,4 @@
using FileTime.Core.Models;
using FileTime.Core.Timeline;
namespace FileTime.App.Core.ViewModels.Timeline;

View File

@@ -23,6 +23,8 @@
<ProjectReference Include="..\..\Core\FileTime.Core.Models\FileTime.Core.Models.csproj" />
<ProjectReference Include="..\..\Providers\FileTime.Providers.Local.Abstractions\FileTime.Providers.Local.Abstractions.csproj" />
<ProjectReference Include="..\..\Tools\FileTime.Tools\FileTime.Tools.csproj" />
<ProjectReference Include="..\FileTime.App.CommandPalette.Abstractions\FileTime.App.CommandPalette.Abstractions.csproj" />
<ProjectReference Include="..\FileTime.App.CommandPalette\FileTime.App.CommandPalette.csproj" />
<ProjectReference Include="..\FileTime.App.Core.Abstraction\FileTime.App.Core.Abstraction.csproj" />
<ProjectReference Include="..\..\Core\FileTime.Core.Command\FileTime.Core.Command.csproj" />
<ProjectReference Include="..\FileTime.App.FrequencyNavigation.Abstractions\FileTime.App.FrequencyNavigation.Abstractions.csproj" />

View File

@@ -3,7 +3,6 @@ using FileTime.App.Core.ViewModels;
using FileTime.App.Core.ViewModels.ItemPreview;
using FileTime.Core.Models;
using InitableService;
using Microsoft.Extensions.DependencyInjection;
namespace FileTime.App.Core.Services;

View File

@@ -16,4 +16,6 @@ public class IdentifiableUserCommandService : IIdentifiableUserCommandService
return _identifiableUserCommands[identifier].Invoke();
}
public IReadOnlyCollection<string> GetCommandIdentifiers() => _identifiableUserCommands.Keys.ToList();
}

View File

@@ -1,5 +1,4 @@
using System.Reactive.Linq;
using FileTime.App.Core.Models;
using FileTime.App.Core.Models.Enums;
using FileTime.App.Core.UserCommand;
using FileTime.App.Core.ViewModels;

View File

@@ -1,3 +1,4 @@
using FileTime.App.CommandPalette.Services;
using FileTime.App.Core.Extensions;
using FileTime.App.Core.Models.Enums;
using FileTime.App.Core.UserCommand;
@@ -22,6 +23,7 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase
private readonly ITimelessContentProvider _timelessContentProvider;
private readonly IUserCommunicationService _userCommunicationService;
private readonly IFrequencyNavigationService _frequencyNavigationService;
private readonly ICommandPaletteService _commandPaletteService;
private ITabViewModel? _selectedTab;
private IContainer? _currentLocation;
private IItemViewModel? _currentSelectedItem;
@@ -35,7 +37,8 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase
IUserCommandHandlerService userCommandHandlerService,
ITimelessContentProvider timelessContentProvider,
IUserCommunicationService userCommunicationService,
IFrequencyNavigationService frequencyNavigationService) : base(appState)
IFrequencyNavigationService frequencyNavigationService,
ICommandPaletteService commandPaletteService) : base(appState)
{
_appState = appState;
_serviceProvider = serviceProvider;
@@ -44,6 +47,7 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase
_timelessContentProvider = timelessContentProvider;
_userCommunicationService = userCommunicationService;
_frequencyNavigationService = frequencyNavigationService;
_commandPaletteService = commandPaletteService;
SaveSelectedTab(t => _selectedTab = t);
SaveCurrentSelectedItem(i => _currentSelectedItem = i);
@@ -69,6 +73,7 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase
new TypeUserCommandHandler<MoveCursorToLastCommand>(MoveCursorToLast),
new TypeUserCommandHandler<MoveCursorUpCommand>(MoveCursorUp),
new TypeUserCommandHandler<MoveCursorUpPageCommand>(MoveCursorUpPage),
new TypeUserCommandHandler<OpenCommandPaletteCommand>(OpenCommandPalette),
new TypeUserCommandHandler<OpenContainerCommand>(OpenContainer),
new TypeUserCommandHandler<OpenSelectedCommand>(OpenSelected),
new TypeUserCommandHandler<RefreshCommand>(Refresh),
@@ -76,6 +81,12 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase
});
}
private Task OpenCommandPalette()
{
_commandPaletteService.OpenCommandPalette();
return Task.CompletedTask;
}
private Task GoByFrequency()
{
_frequencyNavigationService.OpenNavigationWindow();

View File

@@ -1,6 +1,4 @@
using System.Diagnostics;
using System.Security.Cryptography;
using System.Text;
using FileTime.App.Core.UserCommand;
using FileTime.App.Core.ViewModels;
using FileTime.App.Search;

View File

@@ -1,3 +1,4 @@
using FileTime.App.CommandPalette.ViewModels;
using FileTime.App.Core.Services;
using FileTime.App.Core.Services.UserCommandHandler;
using FileTime.App.Core.StartupServices;
@@ -25,6 +26,7 @@ public static class Startup
serviceCollection.TryAddSingleton<IIdentifiableUserCommandService, IdentifiableUserCommandService>();
serviceCollection.TryAddSingleton<IItemPreviewService, ItemPreviewService>();
serviceCollection.TryAddSingleton<ITimelineViewModel, TimelineViewModel>();
serviceCollection.TryAddSingleton<ICommandPaletteViewModel, CommandPaletteViewModel>();
return serviceCollection
.AddCommandHandlers()

View File

@@ -5,11 +5,11 @@ namespace FileTime.App.Core.StartupServices;
public class DefaultIdentifiableCommandHandlerRegister : IStartupHandler
{
private readonly IIdentifiableUserCommandService _service;
private readonly IIdentifiableUserCommandService _userCommandHandlerService;
public DefaultIdentifiableCommandHandlerRegister(IIdentifiableUserCommandService service)
public DefaultIdentifiableCommandHandlerRegister(IIdentifiableUserCommandService userCommandHandlerService)
{
_service = service;
_userCommandHandlerService = userCommandHandlerService;
AddUserCommand(CloseTabCommand.Instance);
AddUserCommand(CopyCommand.Instance);
@@ -34,6 +34,7 @@ public class DefaultIdentifiableCommandHandlerRegister : IStartupHandler
AddUserCommand(MoveCursorToLastCommand.Instance);
AddUserCommand(MoveCursorUpCommand.Instance);
AddUserCommand(MoveCursorUpPageCommand.Instance);
AddUserCommand(OpenCommandPaletteCommand.Instance);
AddUserCommand(OpenInDefaultFileExplorerCommand.Instance);
AddUserCommand(OpenSelectedCommand.Instance);
AddUserCommand(PasteCommand.Merge);
@@ -58,5 +59,5 @@ public class DefaultIdentifiableCommandHandlerRegister : IStartupHandler
public Task InitAsync() => Task.CompletedTask;
private void AddUserCommand(IIdentifiableUserCommand command)
=> _service.AddIdentifiableUserCommandFactory(command.UserCommandID, () => command);
=> _userCommandHandlerService.AddIdentifiableUserCommandFactory(command.UserCommandID, () => command);
}

View File

@@ -2,7 +2,6 @@ using System.Reactive.Linq;
using DynamicData;
using DynamicData.Binding;
using FileTime.App.Core.Extensions;
using FileTime.App.Core.Models;
using FileTime.App.Core.Models.Enums;
using FileTime.App.Core.Services;
using FileTime.Core.Enums;

View File

@@ -19,7 +19,7 @@ public partial class FrequencyNavigationViewModel : IFrequencyNavigationViewMode
[Property] private IObservable<bool> _showWindow;
[Property] private List<string> _filteredMatches;
[Property] private string _selectedItem;
[Property] private string? _selectedItem;
public string SearchText
{
@@ -68,12 +68,12 @@ public partial class FrequencyNavigationViewModel : IFrequencyNavigationViewMode
}
partial void OnInitialize()
=> _showWindow = _frequencyNavigationService.ShowWindow;
=> ShowWindow = _frequencyNavigationService.ShowWindow;
private void UpdateFilteredMatches()
{
FilteredMatches = new List<string>(_frequencyNavigationService.GetMatchingContainers(_searchText));
if (FilteredMatches.Contains(SelectedItem)) return;
if (SelectedItem != null && FilteredMatches.Contains(SelectedItem)) return;
SelectedItem = FilteredMatches.Count > 0
? FilteredMatches[0]

View File

@@ -1,8 +1,6 @@
using System.Reactive.Linq;
using DynamicData;
using FileTime.Core.ContentAccess;
using FileTime.Core.Enums;
using FileTime.Core.Extensions;
using FileTime.Core.Models;
using FileTime.Core.Timeline;