From 6dade011b05ee6c01e24fb081d670f03c02d60d9 Mon Sep 17 00:00:00 2001 From: mika kuns Date: Mon, 20 Apr 2026 10:37:52 +0200 Subject: [PATCH] feat(ui): keyboard shortcuts (/ Ctrl+N Space Esc) Co-Authored-By: Claude Opus 4.7 (1M context) --- .../ViewModels/Islands/ListsIslandViewModel.cs | 2 ++ .../ViewModels/Islands/TasksIslandViewModel.cs | 2 ++ .../ViewModels/IslandsShellViewModel.cs | 13 +++++++++++++ .../Views/Islands/ListsIslandView.axaml | 2 +- .../Views/Islands/ListsIslandView.axaml.cs | 10 +++++++++- .../Views/Islands/TasksIslandView.axaml | 2 +- .../Views/Islands/TasksIslandView.axaml.cs | 11 ++++++++++- src/ClaudeDo.Ui/Views/MainWindow.axaml | 5 +++++ src/ClaudeDo.Ui/Views/MainWindow.axaml.cs | 17 ++++++++++++++++- 9 files changed, 59 insertions(+), 5 deletions(-) diff --git a/src/ClaudeDo.Ui/ViewModels/Islands/ListsIslandViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Islands/ListsIslandViewModel.cs index 69b1383..59614c2 100644 --- a/src/ClaudeDo.Ui/ViewModels/Islands/ListsIslandViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/Islands/ListsIslandViewModel.cs @@ -14,6 +14,8 @@ public sealed partial class ListsIslandViewModel : ViewModelBase private readonly IDbContextFactory _dbFactory; public event EventHandler? SelectionChanged; + public event EventHandler? FocusSearchRequested; + public void RequestFocusSearch() => FocusSearchRequested?.Invoke(this, EventArgs.Empty); public ObservableCollection Items { get; } = new(); diff --git a/src/ClaudeDo.Ui/ViewModels/Islands/TasksIslandViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Islands/TasksIslandViewModel.cs index 1230fc5..cea064d 100644 --- a/src/ClaudeDo.Ui/ViewModels/Islands/TasksIslandViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/Islands/TasksIslandViewModel.cs @@ -14,6 +14,8 @@ public sealed partial class TasksIslandViewModel : ViewModelBase private ListNavItemViewModel? _currentList; public event EventHandler? SelectionChanged; + public event EventHandler? FocusAddTaskRequested; + public void RequestFocusAddTask() => FocusAddTaskRequested?.Invoke(this, EventArgs.Empty); public ObservableCollection Items { get; } = new(); diff --git a/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs b/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs index daabb6a..4fb4cd7 100644 --- a/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs @@ -1,4 +1,5 @@ using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; using ClaudeDo.Ui.ViewModels.Islands; namespace ClaudeDo.Ui.ViewModels; @@ -15,6 +16,18 @@ public sealed partial class IslandsShellViewModel : ViewModelBase public bool ShowDetails => WindowWidth >= 1100; public bool ShowLists => WindowWidth >= 780; + [RelayCommand] + private void FocusSearch() => Lists.RequestFocusSearch(); + + [RelayCommand] + private void FocusAddTask() => Tasks.RequestFocusAddTask(); + + public async Task ToggleSelectedDoneAsync() + { + if (Tasks.SelectedTask is { } row) + await Tasks.ToggleDoneCommand.ExecuteAsync(row); + } + partial void OnWindowWidthChanged(double value) { OnPropertyChanged(nameof(ShowDetails)); diff --git a/src/ClaudeDo.Ui/Views/Islands/ListsIslandView.axaml b/src/ClaudeDo.Ui/Views/Islands/ListsIslandView.axaml index cb01302..79aff3b 100644 --- a/src/ClaudeDo.Ui/Views/Islands/ListsIslandView.axaml +++ b/src/ClaudeDo.Ui/Views/Islands/ListsIslandView.axaml @@ -10,7 +10,7 @@ - diff --git a/src/ClaudeDo.Ui/Views/Islands/ListsIslandView.axaml.cs b/src/ClaudeDo.Ui/Views/Islands/ListsIslandView.axaml.cs index eb560de..2932838 100644 --- a/src/ClaudeDo.Ui/Views/Islands/ListsIslandView.axaml.cs +++ b/src/ClaudeDo.Ui/Views/Islands/ListsIslandView.axaml.cs @@ -6,7 +6,15 @@ namespace ClaudeDo.Ui.Views.Islands; public partial class ListsIslandView : UserControl { - public ListsIslandView() { InitializeComponent(); } + public ListsIslandView() + { + InitializeComponent(); + DataContextChanged += (_, _) => + { + if (DataContext is ListsIslandViewModel vm) + vm.FocusSearchRequested += (_, _) => SearchBox.Focus(); + }; + } private void OnItemTapped(object? sender, RoutedEventArgs e) { diff --git a/src/ClaudeDo.Ui/Views/Islands/TasksIslandView.axaml b/src/ClaudeDo.Ui/Views/Islands/TasksIslandView.axaml index d7e8b2c..c1a95e1 100644 --- a/src/ClaudeDo.Ui/Views/Islands/TasksIslandView.axaml +++ b/src/ClaudeDo.Ui/Views/Islands/TasksIslandView.axaml @@ -20,7 +20,7 @@ - + diff --git a/src/ClaudeDo.Ui/Views/Islands/TasksIslandView.axaml.cs b/src/ClaudeDo.Ui/Views/Islands/TasksIslandView.axaml.cs index 5b7d959..9aff67e 100644 --- a/src/ClaudeDo.Ui/Views/Islands/TasksIslandView.axaml.cs +++ b/src/ClaudeDo.Ui/Views/Islands/TasksIslandView.axaml.cs @@ -1,8 +1,17 @@ using Avalonia.Controls; +using ClaudeDo.Ui.ViewModels.Islands; namespace ClaudeDo.Ui.Views.Islands; public partial class TasksIslandView : UserControl { - public TasksIslandView() { InitializeComponent(); } + public TasksIslandView() + { + InitializeComponent(); + DataContextChanged += (_, _) => + { + if (DataContext is TasksIslandViewModel vm) + vm.FocusAddTaskRequested += (_, _) => AddTaskBox.Focus(); + }; + } } diff --git a/src/ClaudeDo.Ui/Views/MainWindow.axaml b/src/ClaudeDo.Ui/Views/MainWindow.axaml index e929199..18a5c26 100644 --- a/src/ClaudeDo.Ui/Views/MainWindow.axaml +++ b/src/ClaudeDo.Ui/Views/MainWindow.axaml @@ -10,6 +10,11 @@ SystemDecorations="None" ExtendClientAreaToDecorationsHint="True" ExtendClientAreaTitleBarHeightHint="-1"> + + + + + diff --git a/src/ClaudeDo.Ui/Views/MainWindow.axaml.cs b/src/ClaudeDo.Ui/Views/MainWindow.axaml.cs index 282e67b..01a17aa 100644 --- a/src/ClaudeDo.Ui/Views/MainWindow.axaml.cs +++ b/src/ClaudeDo.Ui/Views/MainWindow.axaml.cs @@ -6,7 +6,22 @@ namespace ClaudeDo.Ui.Views; public partial class MainWindow : Window { - public MainWindow() { InitializeComponent(); } + public MainWindow() + { + InitializeComponent(); + KeyDown += OnWindowKeyDown; + } + + private void OnWindowKeyDown(object? sender, KeyEventArgs e) + { + if (e.Key == Key.Space + && FocusManager?.GetFocusedElement() is not TextBox + && DataContext is IslandsShellViewModel vm) + { + e.Handled = true; + _ = vm.ToggleSelectedDoneAsync(); + } + } private void OnTitleBarPressed(object? sender, PointerPressedEventArgs e) {