diff --git a/src/ViewModels/ExecuteCustomActionCommandPalette.cs b/src/ViewModels/ExecuteCustomActionCommandPalette.cs new file mode 100644 index 00000000..8c50b044 --- /dev/null +++ b/src/ViewModels/ExecuteCustomActionCommandPalette.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace SourceGit.ViewModels +{ + public class ExecuteCustomActionCommandPaletteCmd + { + public Models.CustomAction Action { get; set; } + public bool IsGlobal { get; set; } + public string Name { get => Action.Name; } + + public ExecuteCustomActionCommandPaletteCmd(Models.CustomAction action, bool isGlobal) + { + Action = action; + IsGlobal = isGlobal; + } + } + + public class ExecuteCustomActionCommandPalette : ICommandPalette + { + public List VisibleActions + { + get => _visibleActions; + private set => SetProperty(ref _visibleActions, value); + } + + public ExecuteCustomActionCommandPaletteCmd Selected + { + get => _selected; + set => SetProperty(ref _selected, value); + } + + public string Filter + { + get => _filter; + set + { + if (SetProperty(ref _filter, value)) + UpdateVisibleActions(); + } + } + + public ExecuteCustomActionCommandPalette(Launcher launcher, Repository repo) + { + _launcher = launcher; + _repo = repo; + + var actions = repo.GetCustomActions(Models.CustomActionScope.Repository); + foreach (var (action, menu) in actions) + _actions.Add(new(action, menu.IsGlobal)); + + if (_actions.Count > 0) + { + _actions.Sort((l, r) => + { + if (l.IsGlobal != r.IsGlobal) + return l.IsGlobal ? -1 : 1; + + return l.Name.CompareTo(r.Name, StringComparison.OrdinalIgnoreCase); + }); + + _visibleActions = _actions; + _selected = _actions[0]; + } + } + + public override void Cleanup() + { + _launcher = null; + _repo = null; + _actions.Clear(); + _visibleActions.Clear(); + _selected = null; + _filter = null; + } + + public void ClearFilter() + { + Filter = string.Empty; + } + + public async Task ExecAsync() + { + _launcher.CommandPalette = null; + + if (_selected != null) + await _repo.ExecCustomActionAsync(_selected.Action, null); + + Dispose(); + GC.Collect(); + } + + private void UpdateVisibleActions() + { + var filter = _filter?.Trim(); + if (string.IsNullOrEmpty(filter)) + { + VisibleActions = _actions; + return; + } + + var visible = new List(); + foreach (var act in _actions) + { + if (act.Name.Contains(filter, StringComparison.OrdinalIgnoreCase)) + visible.Add(act); + } + + var autoSelected = _selected; + if (visible.Count == 0) + autoSelected = null; + else if (_selected == null || !visible.Contains(_selected)) + autoSelected = visible[0]; + + VisibleActions = visible; + Selected = autoSelected; + } + + private Launcher _launcher; + private Repository _repo; + private List _actions = []; + private List _visibleActions = []; + private ExecuteCustomActionCommandPaletteCmd _selected = null; + private string _filter; + } +} diff --git a/src/ViewModels/RepositoryCommandPalette.cs b/src/ViewModels/RepositoryCommandPalette.cs index 851616b4..cc222443 100644 --- a/src/ViewModels/RepositoryCommandPalette.cs +++ b/src/ViewModels/RepositoryCommandPalette.cs @@ -140,6 +140,12 @@ namespace SourceGit.ViewModels await App.ShowDialog(new RepositoryConfigure(repo)); })); + _cmds.Add(new($"{App.Text("Repository.CustomActions")}...", "custom actions", "Action", async () => + { + var sub = new ExecuteCustomActionCommandPalette(_launcher, _repo); + _launcher.OpenCommandPalette(sub); + })); + _cmds.Sort((l, r) => l.Label.CompareTo(r.Label)); _visibleCmds = _cmds; _selectedCmd = _cmds[0]; diff --git a/src/Views/ExecuteCustomActionCommandPalette.axaml b/src/Views/ExecuteCustomActionCommandPalette.axaml new file mode 100644 index 00000000..e6988955 --- /dev/null +++ b/src/Views/ExecuteCustomActionCommandPalette.axaml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Views/ExecuteCustomActionCommandPalette.axaml.cs b/src/Views/ExecuteCustomActionCommandPalette.axaml.cs new file mode 100644 index 00000000..8986b3b7 --- /dev/null +++ b/src/Views/ExecuteCustomActionCommandPalette.axaml.cs @@ -0,0 +1,63 @@ +using Avalonia.Controls; +using Avalonia.Input; + +namespace SourceGit.Views +{ + public partial class ExecuteCustomActionCommandPalette : UserControl + { + public ExecuteCustomActionCommandPalette() + { + InitializeComponent(); + } + + protected override async void OnKeyDown(KeyEventArgs e) + { + base.OnKeyDown(e); + + if (DataContext is not ViewModels.ExecuteCustomActionCommandPalette vm) + return; + + if (e.Key == Key.Enter) + { + await vm.ExecAsync(); + e.Handled = true; + } + else if (e.Key == Key.Up) + { + if (ActionListBox.IsKeyboardFocusWithin) + { + FilterTextBox.Focus(NavigationMethod.Directional); + e.Handled = true; + return; + } + } + else if (e.Key == Key.Down || e.Key == Key.Tab) + { + if (FilterTextBox.IsKeyboardFocusWithin) + { + if (vm.VisibleActions.Count > 0) + ActionListBox.Focus(NavigationMethod.Directional); + + e.Handled = true; + return; + } + + if (ActionListBox.IsKeyboardFocusWithin && e.Key == Key.Tab) + { + FilterTextBox.Focus(NavigationMethod.Directional); + e.Handled = true; + return; + } + } + } + + private async void OnItemTapped(object sender, TappedEventArgs e) + { + if (DataContext is ViewModels.ExecuteCustomActionCommandPalette vm) + { + await vm.ExecAsync(); + e.Handled = true; + } + } + } +} diff --git a/src/Views/RepositoryCommandPalette.axaml b/src/Views/RepositoryCommandPalette.axaml index f3975c6d..adf59e46 100644 --- a/src/Views/RepositoryCommandPalette.axaml +++ b/src/Views/RepositoryCommandPalette.axaml @@ -50,7 +50,7 @@