feat(explorer): add Avalonia ActionKeywordSetting dialog

- Port ActionKeywordSetting window to Avalonia
- Includes keyword editing, enable/disable toggle, validation
- Handles Enter key to submit, Space key blocked in keyword input
This commit is contained in:
Hongtao Zhang 2026-01-26 16:32:03 -08:00
parent e726c44559
commit e337f9e6c5
2 changed files with 184 additions and 0 deletions

View file

@ -0,0 +1,60 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="255"
x:Class="Flow.Launcher.Plugin.Explorer.Views.Avalonia.ActionKeywordSetting"
Title="{DynamicResource plugin_explorer_manageactionkeywords_header}"
Width="400"
Height="255"
WindowStartupLocation="CenterOwner"
CanResize="False">
<Grid RowDefinitions="*,80">
<StackPanel Grid.Row="0" Margin="26,20,26,0" Spacing="15">
<TextBlock FontSize="20"
FontWeight="SemiBold"
Text="{DynamicResource plugin_explorer_manageactionkeywords_header}" />
<Grid ColumnDefinitions="150,*" RowDefinitions="Auto,Auto" Margin="0,10,0,0">
<TextBlock Grid.Row="0" Grid.Column="0"
VerticalAlignment="Center"
Text="{DynamicResource plugin_explorer_actionkeyword_current}" />
<TextBox Grid.Row="0" Grid.Column="1"
x:Name="TxtCurrentActionKeyword"
Text="{Binding ActionKeyword, Mode=TwoWay}"
KeyDown="TxtCurrentActionKeyword_OnKeyDown" />
<TextBlock Grid.Row="1" Grid.Column="0"
Margin="0,15,0,0"
VerticalAlignment="Center"
Text="{DynamicResource plugin_explorer_actionkeyword_enabled}" />
<CheckBox Grid.Row="1" Grid.Column="1"
Margin="0,15,0,0"
IsChecked="{Binding KeywordEnabled, Mode=TwoWay}"
ToolTip.Tip="{DynamicResource plugin_explorer_actionkeyword_enabled_tooltip}" />
</Grid>
</StackPanel>
<Border Grid.Row="1"
BorderThickness="0,1,0,0"
BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}">
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Spacing="10">
<Button x:Name="btnCancel"
Width="145"
Height="30"
Content="{DynamicResource cancel}"
Click="BtnCancel_OnClick" />
<Button x:Name="btnDone"
Width="145"
Height="30"
Classes="accent"
Content="{DynamicResource plugin_explorer_actionkeyword_done}"
Click="OnDoneButtonClick" />
</StackPanel>
</Border>
</Grid>
</Window>

View file

@ -0,0 +1,124 @@
#nullable enable
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Flow.Launcher.Plugin.Explorer.ViewModels;
namespace Flow.Launcher.Plugin.Explorer.Views.Avalonia;
public partial class ActionKeywordSetting : Window, INotifyPropertyChanged
{
private ActionKeywordModel CurrentActionKeyword { get; }
private string _actionKeyword = string.Empty;
private bool _keywordEnabled;
public string ActionKeyword
{
get => _actionKeyword;
set
{
// Set Enable to be true if user changes ActionKeyword
KeywordEnabled = true;
SetProperty(ref _actionKeyword, value);
}
}
public bool KeywordEnabled
{
get => _keywordEnabled;
set => SetProperty(ref _keywordEnabled, value);
}
public ActionKeywordSetting()
{
CurrentActionKeyword = new ActionKeywordModel(Settings.ActionKeyword.SearchActionKeyword, "");
InitializeComponent();
DataContext = this;
}
public ActionKeywordSetting(ActionKeywordModel selectedActionKeyword)
{
CurrentActionKeyword = selectedActionKeyword;
_actionKeyword = selectedActionKeyword.Keyword;
_keywordEnabled = selectedActionKeyword.Enabled;
InitializeComponent();
DataContext = this;
this.FindControl<TextBox>("TxtCurrentActionKeyword")?.Focus();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void OnDoneButtonClick(object? sender, RoutedEventArgs e)
{
if (string.IsNullOrEmpty(ActionKeyword))
ActionKeyword = Query.GlobalPluginWildcardSign;
if (CurrentActionKeyword.Keyword == ActionKeyword && CurrentActionKeyword.Enabled == KeywordEnabled)
{
Close(false);
return;
}
if (ActionKeyword == Query.GlobalPluginWildcardSign)
switch (CurrentActionKeyword.KeywordProperty, KeywordEnabled)
{
case (Settings.ActionKeyword.FileContentSearchActionKeyword, true):
Main.Context.API.ShowMsgBox(Localize.plugin_explorer_globalActionKeywordInvalid());
return;
case (Settings.ActionKeyword.QuickAccessActionKeyword, true):
Main.Context.API.ShowMsgBox(Localize.plugin_explorer_quickaccess_globalActionKeywordInvalid());
return;
}
if (!KeywordEnabled || !Main.Context.API.ActionKeywordAssigned(ActionKeyword))
{
Close(true);
return;
}
// The keyword is not valid, so show message
Main.Context.API.ShowMsgBox(Localize.plugin_explorer_new_action_keyword_assigned());
}
private void BtnCancel_OnClick(object? sender, RoutedEventArgs e)
{
Close(false);
}
private void TxtCurrentActionKeyword_OnKeyDown(object? sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
OnDoneButtonClick(sender, e);
e.Handled = true;
}
if (e.Key == Key.Space)
{
e.Handled = true;
}
}
#region INotifyPropertyChanged
public new event PropertyChangedEventHandler? PropertyChanged;
private bool SetProperty<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
{
if (Equals(field, value)) return false;
field = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
return true;
}
#endregion
}