Skip to content

Commit 00183fb

Browse files
committed
feature: add CustomAction to command palette (#2165)
Signed-off-by: leo <longshuang@msn.cn>
1 parent 37f9dd1 commit 00183fb

File tree

5 files changed

+315
-1
lines changed

5 files changed

+315
-1
lines changed
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Threading.Tasks;
4+
5+
namespace SourceGit.ViewModels
6+
{
7+
public class ExecuteCustomActionCommandPaletteCmd
8+
{
9+
public Models.CustomAction Action { get; set; }
10+
public bool IsGlobal { get; set; }
11+
public string Name { get => Action.Name; }
12+
13+
public ExecuteCustomActionCommandPaletteCmd(Models.CustomAction action, bool isGlobal)
14+
{
15+
Action = action;
16+
IsGlobal = isGlobal;
17+
}
18+
}
19+
20+
public class ExecuteCustomActionCommandPalette : ICommandPalette
21+
{
22+
public List<ExecuteCustomActionCommandPaletteCmd> VisibleActions
23+
{
24+
get => _visibleActions;
25+
private set => SetProperty(ref _visibleActions, value);
26+
}
27+
28+
public ExecuteCustomActionCommandPaletteCmd Selected
29+
{
30+
get => _selected;
31+
set => SetProperty(ref _selected, value);
32+
}
33+
34+
public string Filter
35+
{
36+
get => _filter;
37+
set
38+
{
39+
if (SetProperty(ref _filter, value))
40+
UpdateVisibleActions();
41+
}
42+
}
43+
44+
public ExecuteCustomActionCommandPalette(Launcher launcher, Repository repo)
45+
{
46+
_launcher = launcher;
47+
_repo = repo;
48+
49+
var actions = repo.GetCustomActions(Models.CustomActionScope.Repository);
50+
foreach (var (action, menu) in actions)
51+
_actions.Add(new(action, menu.IsGlobal));
52+
53+
if (_actions.Count > 0)
54+
{
55+
_actions.Sort((l, r) =>
56+
{
57+
if (l.IsGlobal != r.IsGlobal)
58+
return l.IsGlobal ? -1 : 1;
59+
60+
return l.Name.CompareTo(r.Name, StringComparison.OrdinalIgnoreCase);
61+
});
62+
63+
_visibleActions = _actions;
64+
_selected = _actions[0];
65+
}
66+
}
67+
68+
public override void Cleanup()
69+
{
70+
_launcher = null;
71+
_repo = null;
72+
_actions.Clear();
73+
_visibleActions.Clear();
74+
_selected = null;
75+
_filter = null;
76+
}
77+
78+
public void ClearFilter()
79+
{
80+
Filter = string.Empty;
81+
}
82+
83+
public async Task ExecAsync()
84+
{
85+
_launcher.CommandPalette = null;
86+
87+
if (_selected != null)
88+
await _repo.ExecCustomActionAsync(_selected.Action, null);
89+
90+
Dispose();
91+
GC.Collect();
92+
}
93+
94+
private void UpdateVisibleActions()
95+
{
96+
var filter = _filter?.Trim();
97+
if (string.IsNullOrEmpty(filter))
98+
{
99+
VisibleActions = _actions;
100+
return;
101+
}
102+
103+
var visible = new List<ExecuteCustomActionCommandPaletteCmd>();
104+
foreach (var act in _actions)
105+
{
106+
if (act.Name.Contains(filter, StringComparison.OrdinalIgnoreCase))
107+
visible.Add(act);
108+
}
109+
110+
var autoSelected = _selected;
111+
if (visible.Count == 0)
112+
autoSelected = null;
113+
else if (_selected == null || !visible.Contains(_selected))
114+
autoSelected = visible[0];
115+
116+
VisibleActions = visible;
117+
Selected = autoSelected;
118+
}
119+
120+
private Launcher _launcher;
121+
private Repository _repo;
122+
private List<ExecuteCustomActionCommandPaletteCmd> _actions = [];
123+
private List<ExecuteCustomActionCommandPaletteCmd> _visibleActions = [];
124+
private ExecuteCustomActionCommandPaletteCmd _selected = null;
125+
private string _filter;
126+
}
127+
}

src/ViewModels/RepositoryCommandPalette.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,12 @@ public RepositoryCommandPalette(Launcher launcher, Repository repo)
140140
await App.ShowDialog(new RepositoryConfigure(repo));
141141
}));
142142

143+
_cmds.Add(new($"{App.Text("Repository.CustomActions")}...", "custom actions", "Action", async () =>
144+
{
145+
var sub = new ExecuteCustomActionCommandPalette(_launcher, _repo);
146+
_launcher.OpenCommandPalette(sub);
147+
}));
148+
143149
_cmds.Sort((l, r) => l.Label.CompareTo(r.Label));
144150
_visibleCmds = _cmds;
145151
_selectedCmd = _cmds[0];
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
<UserControl xmlns="https://github.com/avaloniaui"
2+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
3+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
4+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
5+
xmlns:vm="using:SourceGit.ViewModels"
6+
xmlns:v="using:SourceGit.Views"
7+
xmlns:c="using:SourceGit.Converters"
8+
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
9+
x:Class="SourceGit.Views.ExecuteCustomActionCommandPalette"
10+
x:DataType="vm:ExecuteCustomActionCommandPalette">
11+
<Grid RowDefinitions="Auto,Auto">
12+
<v:RepositoryCommandPaletteTextBox Grid.Row="0"
13+
x:Name="FilterTextBox"
14+
Height="24"
15+
Margin="4,8,4,0"
16+
BorderThickness="1"
17+
CornerRadius="12"
18+
Text="{Binding Filter, Mode=TwoWay}"
19+
BorderBrush="{DynamicResource Brush.Border2}"
20+
VerticalContentAlignment="Center">
21+
<TextBox.InnerLeftContent>
22+
<StackPanel Orientation="Horizontal">
23+
<Path Width="14" Height="14"
24+
Margin="6,0,0,0"
25+
Fill="{DynamicResource Brush.FG2}"
26+
Data="{StaticResource Icons.Search}"/>
27+
<Border BorderThickness="0"
28+
Background="{DynamicResource Brush.Badge}"
29+
Height="18"
30+
CornerRadius="4"
31+
Margin="4,0,0,0" Padding="4,0">
32+
<TextBlock Text="{DynamicResource Text.Repository.CustomActions}"
33+
Foreground="Black"
34+
FontWeight="Bold"/>
35+
</Border>
36+
</StackPanel>
37+
</TextBox.InnerLeftContent>
38+
39+
<TextBox.InnerRightContent>
40+
<Button Classes="icon_button"
41+
Width="16"
42+
Margin="0,0,6,0"
43+
Command="{Binding ClearFilter}"
44+
IsVisible="{Binding Filter, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
45+
HorizontalAlignment="Right">
46+
<Path Width="14" Height="14"
47+
Margin="0,1,0,0"
48+
Fill="{DynamicResource Brush.FG1}"
49+
Data="{StaticResource Icons.Clear}"/>
50+
</Button>
51+
</TextBox.InnerRightContent>
52+
</v:RepositoryCommandPaletteTextBox>
53+
54+
<ListBox Grid.Row="1"
55+
x:Name="ActionListBox"
56+
MaxHeight="250"
57+
Margin="4,8,4,0"
58+
BorderThickness="0"
59+
SelectionMode="Single"
60+
Background="Transparent"
61+
Focusable="True"
62+
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
63+
ScrollViewer.VerticalScrollBarVisibility="Auto"
64+
ItemsSource="{Binding VisibleActions, Mode=OneWay}"
65+
SelectedItem="{Binding Selected, Mode=TwoWay}"
66+
IsVisible="{Binding VisibleActions, Converter={x:Static c:ListConverters.IsNotNullOrEmpty}}">
67+
<ListBox.Styles>
68+
<Style Selector="ListBoxItem">
69+
<Setter Property="Padding" Value="8,0"/>
70+
<Setter Property="MinHeight" Value="26"/>
71+
<Setter Property="CornerRadius" Value="4"/>
72+
</Style>
73+
74+
<Style Selector="ListBox">
75+
<Setter Property="FocusAdorner">
76+
<FocusAdornerTemplate>
77+
<Grid/>
78+
</FocusAdornerTemplate>
79+
</Setter>
80+
</Style>
81+
</ListBox.Styles>
82+
83+
<ListBox.ItemsPanel>
84+
<ItemsPanelTemplate>
85+
<VirtualizingStackPanel Orientation="Vertical"/>
86+
</ItemsPanelTemplate>
87+
</ListBox.ItemsPanel>
88+
89+
<ListBox.ItemTemplate>
90+
<DataTemplate DataType="vm:ExecuteCustomActionCommandPaletteCmd">
91+
<Grid ColumnDefinitions="Auto,*,Auto" Background="Transparent" Tapped="OnItemTapped">
92+
<Path Grid.Column="0"
93+
Width="12" Height="12"
94+
Data="{StaticResource Icons.Action}"
95+
IsHitTestVisible="False"/>
96+
<TextBlock Grid.Column="1"
97+
Margin="4,0,0,0"
98+
VerticalAlignment="Center"
99+
IsHitTestVisible="False"
100+
Text="{Binding Name, Mode=OneWay}"/>
101+
<Border Grid.Column="2" Margin="4,0,0,0" Height="16" Background="Green" CornerRadius="8" VerticalAlignment="Center" IsVisible="{Binding IsGlobal}">
102+
<TextBlock Text="GLOBAL" Margin="8,0" FontSize="10" Foreground="White"/>
103+
</Border>
104+
</Grid>
105+
</DataTemplate>
106+
</ListBox.ItemTemplate>
107+
</ListBox>
108+
109+
<Path Grid.Row="1"
110+
Width="64" Height="64"
111+
Margin="0,16,0,0"
112+
HorizontalAlignment="Center"
113+
VerticalAlignment="Center"
114+
Fill="{DynamicResource Brush.FG2}"
115+
Data="{StaticResource Icons.Empty}"
116+
IsVisible="{Binding VisibleActions, Mode=OneWay, Converter={x:Static c:ListConverters.IsNullOrEmpty}}"/>
117+
</Grid>
118+
</UserControl>
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using Avalonia.Controls;
2+
using Avalonia.Input;
3+
4+
namespace SourceGit.Views
5+
{
6+
public partial class ExecuteCustomActionCommandPalette : UserControl
7+
{
8+
public ExecuteCustomActionCommandPalette()
9+
{
10+
InitializeComponent();
11+
}
12+
13+
protected override async void OnKeyDown(KeyEventArgs e)
14+
{
15+
base.OnKeyDown(e);
16+
17+
if (DataContext is not ViewModels.ExecuteCustomActionCommandPalette vm)
18+
return;
19+
20+
if (e.Key == Key.Enter)
21+
{
22+
await vm.ExecAsync();
23+
e.Handled = true;
24+
}
25+
else if (e.Key == Key.Up)
26+
{
27+
if (ActionListBox.IsKeyboardFocusWithin)
28+
{
29+
FilterTextBox.Focus(NavigationMethod.Directional);
30+
e.Handled = true;
31+
return;
32+
}
33+
}
34+
else if (e.Key == Key.Down || e.Key == Key.Tab)
35+
{
36+
if (FilterTextBox.IsKeyboardFocusWithin)
37+
{
38+
if (vm.VisibleActions.Count > 0)
39+
ActionListBox.Focus(NavigationMethod.Directional);
40+
41+
e.Handled = true;
42+
return;
43+
}
44+
45+
if (ActionListBox.IsKeyboardFocusWithin && e.Key == Key.Tab)
46+
{
47+
FilterTextBox.Focus(NavigationMethod.Directional);
48+
e.Handled = true;
49+
return;
50+
}
51+
}
52+
}
53+
54+
private async void OnItemTapped(object sender, TappedEventArgs e)
55+
{
56+
if (DataContext is ViewModels.ExecuteCustomActionCommandPalette vm)
57+
{
58+
await vm.ExecAsync();
59+
e.Handled = true;
60+
}
61+
}
62+
}
63+
}

src/Views/RepositoryCommandPalette.axaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050

5151
<ListBox Grid.Row="2"
5252
x:Name="CmdListBox"
53-
MaxHeight="400"
53+
MaxHeight="420"
5454
Margin="4,0"
5555
BorderThickness="0"
5656
SelectionMode="Single"

0 commit comments

Comments
 (0)