diff --git a/schema/v1/ScriptRunnerSchema.json b/schema/v1/ScriptRunnerSchema.json index 02560af..2197d26 100644 --- a/schema/v1/ScriptRunnerSchema.json +++ b/schema/v1/ScriptRunnerSchema.json @@ -183,6 +183,13 @@ "properties": { "options": { "type": "string" + }, + "searchable": { + "type": "boolean" + }, + "optionsGeneratorCommand": + { + "type": "string" } } } diff --git a/src/ScriptRunner/ScriptRunner.GUI/Parameters/CheckboxControl.cs b/src/ScriptRunner/ScriptRunner.GUI/Parameters/CheckboxControl.cs new file mode 100644 index 0000000..b094dc2 --- /dev/null +++ b/src/ScriptRunner/ScriptRunner.GUI/Parameters/CheckboxControl.cs @@ -0,0 +1,17 @@ +using Avalonia.Controls; + +namespace ScriptRunner.GUI; + +public class CheckboxControl : IControlRecord +{ + public Control Control { get; set; } + public string CheckedValue { get; set; } = "true"; + public string UncheckedValue { get; set; } = "false"; + public string GetFormattedValue() + { + return ((CheckBox)Control).IsChecked == true ? CheckedValue: UncheckedValue; + } + + public string Name { get; set; } + public bool MaskingRequired { get; set; } +} \ No newline at end of file diff --git a/src/ScriptRunner/ScriptRunner.GUI/Parameters/DatePickerControl.cs b/src/ScriptRunner/ScriptRunner.GUI/Parameters/DatePickerControl.cs new file mode 100644 index 0000000..158a282 --- /dev/null +++ b/src/ScriptRunner/ScriptRunner.GUI/Parameters/DatePickerControl.cs @@ -0,0 +1,30 @@ +using System.Globalization; +using Avalonia.Controls; + +namespace ScriptRunner.GUI; + +public class DatePickerControl : IControlRecord +{ + public Control Control { get; set; } + + public string GetFormattedValue() + { + var selectedDateTime = Control switch + { + DatePicker dp => dp.SelectedDate?.DateTime, + CalendarDatePicker cdp => cdp.SelectedDate?.Date, + _ => null + }; + if (string.IsNullOrWhiteSpace(Format) == false && selectedDateTime is {} value) + { + return value.ToString(Format, Culture); + } + return selectedDateTime?.ToString() ?? string.Empty; + } + + public string Name { get; set; } + public bool MaskingRequired { get; set; } + + public string? Format { get; set; } + public CultureInfo Culture { get; set; } +} \ No newline at end of file diff --git a/src/ScriptRunner/ScriptRunner.GUI/Parameters/DirectoryPickerControl.cs b/src/ScriptRunner/ScriptRunner.GUI/Parameters/DirectoryPickerControl.cs new file mode 100644 index 0000000..6918cd0 --- /dev/null +++ b/src/ScriptRunner/ScriptRunner.GUI/Parameters/DirectoryPickerControl.cs @@ -0,0 +1,17 @@ +using Avalonia.Controls; +using ScriptRunner.GUI.Views; + +namespace ScriptRunner.GUI; + +public class DirectoryPickerControl : IControlRecord +{ + public Control Control { get; set; } + + public string GetFormattedValue() + { + return ((DirectoryPicker)Control).DirPath; + } + + public string Name { get; set; } + public bool MaskingRequired { get; set; } +} \ No newline at end of file diff --git a/src/ScriptRunner/ScriptRunner.GUI/Parameters/DropdownControl.cs b/src/ScriptRunner/ScriptRunner.GUI/Parameters/DropdownControl.cs new file mode 100644 index 0000000..d089fdd --- /dev/null +++ b/src/ScriptRunner/ScriptRunner.GUI/Parameters/DropdownControl.cs @@ -0,0 +1,23 @@ +using Avalonia.Controls; +using ScriptRunner.GUI.Views; + +namespace ScriptRunner.GUI; + +public class DropdownControl : IControlRecord +{ + public Control Control { get; set; } + public Control InputControl { get; set; } + + public string GetFormattedValue() + { + return InputControl switch + { + ComboBox cb => cb.SelectedItem?.ToString(), + SearchableComboBox acb => acb.SelectedItem, + _ => "" + } ?? string.Empty; + } + + public string Name { get; set; } + public bool MaskingRequired { get; set; } +} \ No newline at end of file diff --git a/src/ScriptRunner/ScriptRunner.GUI/Parameters/FileContent.cs b/src/ScriptRunner/ScriptRunner.GUI/Parameters/FileContent.cs new file mode 100644 index 0000000..08c19ed --- /dev/null +++ b/src/ScriptRunner/ScriptRunner.GUI/Parameters/FileContent.cs @@ -0,0 +1,40 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using System.Text; +using Avalonia.Controls; + +namespace ScriptRunner.GUI; + +public class FileContent : IControlRecord +{ + private readonly string _extension; + public Control Control { get; set; } + public string FileName { get; set; } + + public FileContent(string extension) + { + _extension = extension; + FileName = Path.GetTempFileName() + "." + extension; + } + + public string GetFormattedValue() + { + var fileContent = ((TextBox)Control).Text; + var hash = string.IsNullOrWhiteSpace(fileContent)? "EMPTY" : ComputeSHA256(fileContent).Substring(0,10); + FileName = Path.Combine(Path.GetTempPath(), hash + "." + _extension); + File.WriteAllText(FileName, fileContent, Encoding.UTF8); + return FileName; + } + + static string ComputeSHA256(string input) + { + using var sha256 = SHA256.Create(); + byte[] bytes = Encoding.UTF8.GetBytes(input); + byte[] hashBytes = sha256.ComputeHash(bytes); + return BitConverter.ToString(hashBytes).Replace("-", "").ToLower(); + } + + public string Name { get; set; } + public bool MaskingRequired { get; set; } +} \ No newline at end of file diff --git a/src/ScriptRunner/ScriptRunner.GUI/Parameters/FilePickerControl.cs b/src/ScriptRunner/ScriptRunner.GUI/Parameters/FilePickerControl.cs new file mode 100644 index 0000000..1f43f06 --- /dev/null +++ b/src/ScriptRunner/ScriptRunner.GUI/Parameters/FilePickerControl.cs @@ -0,0 +1,17 @@ +using Avalonia.Controls; +using ScriptRunner.GUI.Views; + +namespace ScriptRunner.GUI; + +public class FilePickerControl : IControlRecord +{ + public Control Control { get; set; } + + public string GetFormattedValue() + { + return ((FilePicker)Control).FilePath; + } + + public string Name { get; set; } + public bool MaskingRequired { get; set; } +} \ No newline at end of file diff --git a/src/ScriptRunner/ScriptRunner.GUI/Parameters/IControlRecord.cs b/src/ScriptRunner/ScriptRunner.GUI/Parameters/IControlRecord.cs new file mode 100644 index 0000000..80c6d21 --- /dev/null +++ b/src/ScriptRunner/ScriptRunner.GUI/Parameters/IControlRecord.cs @@ -0,0 +1,14 @@ +using Avalonia.Controls; + +namespace ScriptRunner.GUI; + +public interface IControlRecord +{ + Control Control { get; set; } + + string GetFormattedValue(); + + public string Name { get; set; } + + public bool MaskingRequired { get; set; } +} \ No newline at end of file diff --git a/src/ScriptRunner/ScriptRunner.GUI/Parameters/JobStatusToColorConverter.cs b/src/ScriptRunner/ScriptRunner.GUI/Parameters/JobStatusToColorConverter.cs new file mode 100644 index 0000000..89afd97 --- /dev/null +++ b/src/ScriptRunner/ScriptRunner.GUI/Parameters/JobStatusToColorConverter.cs @@ -0,0 +1,36 @@ +using System; +using System.Globalization; +using Avalonia.Data; +using Avalonia.Data.Converters; +using Avalonia.Media; +using ScriptRunner.GUI.ViewModels; + +namespace ScriptRunner.GUI; + +public class JobStatusToColorConverter: IValueConverter +{ + + public static JobStatusToColorConverter Instance { get; } = new(); + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is RunningJobStatus status) + { + return status switch + { + RunningJobStatus.NotStarted => new SolidColorBrush(Colors.Black), + RunningJobStatus.Running => new SolidColorBrush(Colors.LightGreen), + RunningJobStatus.Cancelled => new SolidColorBrush(Colors.Yellow), + RunningJobStatus.Failed => new SolidColorBrush(Colors.Red), + RunningJobStatus.Finished => new SolidColorBrush(Colors.Gray), + _ => throw new ArgumentOutOfRangeException() + }; + } + + return new BindingNotification(new InvalidCastException(), BindingErrorType.Error); + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } +} \ No newline at end of file diff --git a/src/ScriptRunner/ScriptRunner.GUI/Parameters/MultiSelectControl.cs b/src/ScriptRunner/ScriptRunner.GUI/Parameters/MultiSelectControl.cs new file mode 100644 index 0000000..cf9a196 --- /dev/null +++ b/src/ScriptRunner/ScriptRunner.GUI/Parameters/MultiSelectControl.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using Avalonia.Controls; + +namespace ScriptRunner.GUI; + +public class MultiSelectControl : IControlRecord +{ + public Control Control { get; set; } + + public string GetFormattedValue() + { + var selectedItems = ((ListBox)Control).SelectedItems; + var copy = new List(); + foreach (var item in selectedItems) + { + if (item.ToString() is { } nonNullItem) + { + copy.Add(nonNullItem); + } + } + + return string.Join(Delimiter, copy); + } + + public string Name { get; set; } + public bool MaskingRequired { get; set; } + public string Delimiter { get; set; } + +} \ No newline at end of file diff --git a/src/ScriptRunner/ScriptRunner.GUI/Parameters/NumericControl.cs b/src/ScriptRunner/ScriptRunner.GUI/Parameters/NumericControl.cs new file mode 100644 index 0000000..a4056ec --- /dev/null +++ b/src/ScriptRunner/ScriptRunner.GUI/Parameters/NumericControl.cs @@ -0,0 +1,16 @@ +using Avalonia.Controls; + +namespace ScriptRunner.GUI; + +public class NumericControl : IControlRecord +{ + public Control Control { get; set; } + + public string GetFormattedValue() + { + return ((NumericUpDown)Control).Text; + } + + public string Name { get; set; } + public bool MaskingRequired { get; set; } +} \ No newline at end of file diff --git a/src/ScriptRunner/ScriptRunner.GUI/Parameters/ParamsPanel.cs b/src/ScriptRunner/ScriptRunner.GUI/Parameters/ParamsPanel.cs new file mode 100644 index 0000000..42ceccc --- /dev/null +++ b/src/ScriptRunner/ScriptRunner.GUI/Parameters/ParamsPanel.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using Avalonia.Controls; + +namespace ScriptRunner.GUI; + +public class ParamsPanel +{ + public Panel Panel { get; set; } + + public IEnumerable ControlRecords { get; set; } +} \ No newline at end of file diff --git a/src/ScriptRunner/ScriptRunner.GUI/ParamsPanelFactory.cs b/src/ScriptRunner/ScriptRunner.GUI/Parameters/ParamsPanelFactory.cs similarity index 70% rename from src/ScriptRunner/ScriptRunner.GUI/ParamsPanelFactory.cs rename to src/ScriptRunner/ScriptRunner.GUI/Parameters/ParamsPanelFactory.cs index 5644e9d..647cae0 100644 --- a/src/ScriptRunner/ScriptRunner.GUI/ParamsPanelFactory.cs +++ b/src/ScriptRunner/ScriptRunner.GUI/Parameters/ParamsPanelFactory.cs @@ -1,17 +1,14 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Globalization; using System.IO; using System.Linq; -using System.Security.Cryptography; -using System.Text; using System.Threading.Tasks; using Avalonia; using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Controls.Shapes; -using Avalonia.Data; -using Avalonia.Data.Converters; using Avalonia.Input; using Avalonia.Layout; using Avalonia.Media; @@ -51,7 +48,7 @@ public ParamsPanel Create(ScriptConfig action, Dictionary values foreach (var (param,i) in action.Params.Select((x,i)=>(x,i))) { values.TryGetValue(param.Name, out var value); - var controlRecord = CreateControlRecord(param, value, i, action, secretBindings); + var controlRecord = CreateControlRecord(param, value, i, action, secretBindings, commandExecutor); controlRecord.Name = param.Name; if (controlRecord.Control is Layoutable l) { @@ -181,7 +178,8 @@ public ParamsPanel Create(ScriptConfig action, Dictionary values } private IControlRecord CreateControlRecord(ScriptParam p, string? value, int index, - ScriptConfig scriptConfig, List secretBindings) + ScriptConfig scriptConfig, List secretBindings, + Func> commandExecutor) { switch (p.Prompt) { @@ -241,17 +239,76 @@ private IControlRecord CreateControlRecord(ScriptParam p, string? value, int ind MaskingRequired = true, }; case PromptType.Dropdown: - return new DropdownControl + var initialOptions = p.GetPromptSettings("options", out var options) ? options.Split(","):Array.Empty(); + var observableOptions = new ObservableCollection(initialOptions); + var searchable = p.GetPromptSettings("searchable", bool.Parse, false); + var optionsGeneratorCommand = p.GetPromptSettings("optionsGeneratorCommand", out var optionsGeneratorCommandText) ? optionsGeneratorCommandText : null; + + + if (observableOptions.Count == 0 && string.IsNullOrWhiteSpace(value) == false && string.IsNullOrWhiteSpace(optionsGeneratorCommand) == false) { - Control = new ComboBox - { - ItemsSource = p.GetPromptSettings("options", out var options) ? options.Split(","):Array.Empty(), - SelectedItem = value, - TabIndex = index, - IsTabStop = true, - Width = 500 + observableOptions.Add(value); + } + + Control inputControl = searchable ? new SearchableComboBox() + { + Items = observableOptions, + SelectedItem = value, + TabIndex = index, + IsTabStop = true, + Width = 500 + }: new ComboBox + { + ItemsSource = observableOptions, + SelectedItem = value, + TabIndex = index, + IsTabStop = true, + Width = 500 + }; + var actionPanel = new StackPanel() + { + Orientation = Orientation.Horizontal, + Spacing = 5, + Children = + { + inputControl } }; + if (string.IsNullOrWhiteSpace(optionsGeneratorCommand) == false) + { + var generateButton = new Button() + { + Margin = new(5,0,5,0), + Width = 32, + VerticalAlignment = VerticalAlignment.Stretch, + HorizontalContentAlignment = HorizontalAlignment.Center + }; + generateButton.Click += async(sender, args) => + { + generateButton.IsEnabled = false; + generateButton.Classes.Add("spinning"); + var result = await commandExecutor($"Generate options for '{p.Name}'", optionsGeneratorCommand) ?? ""; + Dispatcher.UIThread.Post(() => + { + observableOptions.Clear(); + foreach (var option in result.Split(new[]{'\r', '\n',','}, StringSplitOptions.RemoveEmptyEntries).Distinct().OrderBy(x=>x)) + { + observableOptions.Add(option); + } + generateButton.Classes.Remove("spinning"); + generateButton.IsEnabled = true; + }); + }; + Attached.SetIcon(generateButton, "fas fa-sync"); + ToolTip.SetTip(generateButton, "Refresh available options"); + actionPanel.Children.Add(generateButton); + } + + return new DropdownControl + { + Control = actionPanel, + InputControl = inputControl + }; case PromptType.Multiselect: var delimiter = p.GetPromptSettings("delimiter", s => s, ","); return new MultiSelectControl @@ -413,243 +470,4 @@ private IControlRecord CreateControlRecord(ScriptParam p, string? value, int ind throw new ArgumentOutOfRangeException(nameof(p.Prompt), p.Prompt, null); } } -} - -public class ParamsPanel -{ - public Panel Panel { get; set; } - - public IEnumerable ControlRecords { get; set; } -} - -public class CheckboxControl : IControlRecord -{ - public Control Control { get; set; } - public string CheckedValue { get; set; } = "true"; - public string UncheckedValue { get; set; } = "false"; - public string GetFormattedValue() - { - return ((CheckBox)Control).IsChecked == true ? CheckedValue: UncheckedValue; - } - - public string Name { get; set; } - public bool MaskingRequired { get; set; } -} - -public class DatePickerControl : IControlRecord -{ - public Control Control { get; set; } - - public string GetFormattedValue() - { - var selectedDateTime = Control switch - { - DatePicker dp => dp.SelectedDate?.DateTime, - CalendarDatePicker cdp => cdp.SelectedDate?.Date, - _ => null - }; - if (string.IsNullOrWhiteSpace(Format) == false && selectedDateTime is {} value) - { - return value.ToString(Format, Culture); - } - return selectedDateTime?.ToString() ?? string.Empty; - } - - public string Name { get; set; } - public bool MaskingRequired { get; set; } - - public string? Format { get; set; } - public CultureInfo Culture { get; set; } -} - -public class TimePickerControl : IControlRecord -{ - public Control Control { get; set; } - - public string GetFormattedValue() - { - var selectedTime = ((TimePicker)Control).SelectedTime; - if (string.IsNullOrWhiteSpace(Format) == false && selectedTime is {} value) - { - return value.ToString(Format); - } - return selectedTime?.ToString() ?? string.Empty; - } - - public string Name { get; set; } - public bool MaskingRequired { get; set; } - - public string? Format { get; set; } -} - -public class DropdownControl : IControlRecord -{ - public Control Control { get; set; } - - public string GetFormattedValue() - { - return ((ComboBox)Control).SelectedItem?.ToString(); - } - - public string Name { get; set; } - public bool MaskingRequired { get; set; } -} - -public class TextControl : IControlRecord -{ - public Control Control { get; set; } - - public string GetFormattedValue() - { - return ((TextBox)Control).Text; - } - - public string Name { get; set; } - public bool MaskingRequired { get; set; } -} -public class PasswordControl : IControlRecord -{ - public Control Control { get; set; } - - public string GetFormattedValue() - { - return ((PasswordBox)Control).Password; - } - - public string Name { get; set; } - public bool MaskingRequired { get; set; } -} -public class FileContent : IControlRecord -{ - private readonly string _extension; - public Control Control { get; set; } - public string FileName { get; set; } - - public FileContent(string extension) - { - _extension = extension; - FileName = Path.GetTempFileName() + "." + extension; - } - - public string GetFormattedValue() - { - var fileContent = ((TextBox)Control).Text; - var hash = string.IsNullOrWhiteSpace(fileContent)? "EMPTY" : ComputeSHA256(fileContent).Substring(0,10); - FileName = Path.Combine(Path.GetTempPath(), hash + "." + _extension); - File.WriteAllText(FileName, fileContent, Encoding.UTF8); - return FileName; - } - - static string ComputeSHA256(string input) - { - using var sha256 = SHA256.Create(); - byte[] bytes = Encoding.UTF8.GetBytes(input); - byte[] hashBytes = sha256.ComputeHash(bytes); - return BitConverter.ToString(hashBytes).Replace("-", "").ToLower(); - } - - public string Name { get; set; } - public bool MaskingRequired { get; set; } -} - -public class MultiSelectControl : IControlRecord -{ - public Control Control { get; set; } - - public string GetFormattedValue() - { - var selectedItems = ((ListBox)Control).SelectedItems; - var copy = new List(); - foreach (var item in selectedItems) - { - if (item.ToString() is { } nonNullItem) - { - copy.Add(nonNullItem); - } - } - - return string.Join(Delimiter, copy); - } - - public string Name { get; set; } - public bool MaskingRequired { get; set; } - public string Delimiter { get; set; } - -} -public class FilePickerControl : IControlRecord -{ - public Control Control { get; set; } - - public string GetFormattedValue() - { - return ((FilePicker)Control).FilePath; - } - - public string Name { get; set; } - public bool MaskingRequired { get; set; } -} - -public class DirectoryPickerControl : IControlRecord -{ - public Control Control { get; set; } - - public string GetFormattedValue() - { - return ((DirectoryPicker)Control).DirPath; - } - - public string Name { get; set; } - public bool MaskingRequired { get; set; } -} - -public class NumericControl : IControlRecord -{ - public Control Control { get; set; } - - public string GetFormattedValue() - { - return ((NumericUpDown)Control).Text; - } - - public string Name { get; set; } - public bool MaskingRequired { get; set; } -} - -public interface IControlRecord -{ - Control Control { get; set; } - - string GetFormattedValue(); - - public string Name { get; set; } - - public bool MaskingRequired { get; set; } -} - -public class JobStatusToColorConverter: IValueConverter -{ - - public static JobStatusToColorConverter Instance { get; } = new(); - public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) - { - if (value is RunningJobStatus status) - { - return status switch - { - RunningJobStatus.NotStarted => new SolidColorBrush(Colors.Black), - RunningJobStatus.Running => new SolidColorBrush(Colors.LightGreen), - RunningJobStatus.Cancelled => new SolidColorBrush(Colors.Yellow), - RunningJobStatus.Failed => new SolidColorBrush(Colors.Red), - RunningJobStatus.Finished => new SolidColorBrush(Colors.Gray), - _ => throw new ArgumentOutOfRangeException() - }; - } - - return new BindingNotification(new InvalidCastException(), BindingErrorType.Error); - } - - public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) - { - throw new NotSupportedException(); - } } \ No newline at end of file diff --git a/src/ScriptRunner/ScriptRunner.GUI/Parameters/PasswordControl.cs b/src/ScriptRunner/ScriptRunner.GUI/Parameters/PasswordControl.cs new file mode 100644 index 0000000..bbe716f --- /dev/null +++ b/src/ScriptRunner/ScriptRunner.GUI/Parameters/PasswordControl.cs @@ -0,0 +1,17 @@ +using Avalonia.Controls; +using ScriptRunner.GUI.Views; + +namespace ScriptRunner.GUI; + +public class PasswordControl : IControlRecord +{ + public Control Control { get; set; } + + public string GetFormattedValue() + { + return ((PasswordBox)Control).Password; + } + + public string Name { get; set; } + public bool MaskingRequired { get; set; } +} \ No newline at end of file diff --git a/src/ScriptRunner/ScriptRunner.GUI/Parameters/TextControl.cs b/src/ScriptRunner/ScriptRunner.GUI/Parameters/TextControl.cs new file mode 100644 index 0000000..781e216 --- /dev/null +++ b/src/ScriptRunner/ScriptRunner.GUI/Parameters/TextControl.cs @@ -0,0 +1,16 @@ +using Avalonia.Controls; + +namespace ScriptRunner.GUI; + +public class TextControl : IControlRecord +{ + public Control Control { get; set; } + + public string GetFormattedValue() + { + return ((TextBox)Control).Text; + } + + public string Name { get; set; } + public bool MaskingRequired { get; set; } +} \ No newline at end of file diff --git a/src/ScriptRunner/ScriptRunner.GUI/Parameters/TimePickerControl.cs b/src/ScriptRunner/ScriptRunner.GUI/Parameters/TimePickerControl.cs new file mode 100644 index 0000000..4e60672 --- /dev/null +++ b/src/ScriptRunner/ScriptRunner.GUI/Parameters/TimePickerControl.cs @@ -0,0 +1,23 @@ +using Avalonia.Controls; + +namespace ScriptRunner.GUI; + +public class TimePickerControl : IControlRecord +{ + public Control Control { get; set; } + + public string GetFormattedValue() + { + var selectedTime = ((TimePicker)Control).SelectedTime; + if (string.IsNullOrWhiteSpace(Format) == false && selectedTime is {} value) + { + return value.ToString(Format); + } + return selectedTime?.ToString() ?? string.Empty; + } + + public string Name { get; set; } + public bool MaskingRequired { get; set; } + + public string? Format { get; set; } +} \ No newline at end of file diff --git a/src/ScriptRunner/ScriptRunner.GUI/ScriptConfigs/ScriptConfig.cs b/src/ScriptRunner/ScriptRunner.GUI/ScriptConfigs/ScriptConfig.cs index 55fa6a1..b80e029 100644 --- a/src/ScriptRunner/ScriptRunner.GUI/ScriptConfigs/ScriptConfig.cs +++ b/src/ScriptRunner/ScriptRunner.GUI/ScriptConfigs/ScriptConfig.cs @@ -64,7 +64,7 @@ public class ScriptParam public string Description { get; set; } public PromptType Prompt { get; set; } public string Default { get; set; } - public Dictionary PromptSettings { get; set; } = new(); + public Dictionary PromptSettings { get; set; } = new(); public string? AutoParameterBuilderPattern { get; set; } public string? ValueGeneratorCommand { get; set; } public string? ValueGeneratorLabel { get; set; } @@ -72,14 +72,16 @@ public class ScriptParam public bool GetPromptSettings(string name, [NotNullWhen(true)] out string? value) { - return PromptSettings.TryGetValue(name, out value); + var res = PromptSettings.TryGetValue(name, out var objV); + value = objV?.ToString(); + return res; } public T GetPromptSettings(string name, Func convert, T @default) { if (PromptSettings.TryGetValue(name, out var value)) { - return convert(value); + return convert(value.ToString()!); } return @default; diff --git a/src/ScriptRunner/ScriptRunner.GUI/Themes/StyleClasses.axaml b/src/ScriptRunner/ScriptRunner.GUI/Themes/StyleClasses.axaml index 1939d11..2d28ef0 100644 --- a/src/ScriptRunner/ScriptRunner.GUI/Themes/StyleClasses.axaml +++ b/src/ScriptRunner/ScriptRunner.GUI/Themes/StyleClasses.axaml @@ -62,7 +62,16 @@ - + + + + + + diff --git a/src/ScriptRunner/ScriptRunner.GUI/Views/SearchableComboBox.axaml.cs b/src/ScriptRunner/ScriptRunner.GUI/Views/SearchableComboBox.axaml.cs new file mode 100644 index 0000000..3d33ce2 --- /dev/null +++ b/src/ScriptRunner/ScriptRunner.GUI/Views/SearchableComboBox.axaml.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.ObjectModel; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Markup.Xaml; + +namespace ScriptRunner.GUI.Views; + + public partial class SearchableComboBox : UserControl + { + public static readonly StyledProperty> ItemsProperty = + AvaloniaProperty.Register>(nameof(Items)); + + public static readonly StyledProperty SelectedItemProperty = + AvaloniaProperty.Register(nameof(SelectedItem)); + + private AutoCompleteBox? _autoCompleteBox; + + public SearchableComboBox() + { + Items = new ObservableCollection(); + ItemsProperty.Changed.Subscribe(args => + { + if(args.Sender is SearchableComboBox searchableComboBox) + { + if(searchableComboBox._autoCompleteBox != null) + { + searchableComboBox._autoCompleteBox.ItemsSource = args.NewValue.Value; + } + } + + }); + SelectedItemProperty.Changed.Subscribe(args => + { + if(args.Sender is SearchableComboBox searchableComboBox) + { + if(searchableComboBox._autoCompleteBox != null) + { + searchableComboBox._autoCompleteBox.SelectedItem = args.NewValue.Value; + } + } + }); + this.InitializeComponent(); + } + + public ObservableCollection Items + { + get => GetValue(ItemsProperty); + set => SetValue(ItemsProperty, value); + } + + public string SelectedItem + { + get => GetValue(SelectedItemProperty); + set + { + if (Items.Contains(value)) + { + SetValue(SelectedItemProperty, value); + } + } + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + _autoCompleteBox = this.FindControl("PART_AutoCompleteBox"); + + if (_autoCompleteBox != null) + { + _autoCompleteBox.ItemsSource = Items; + _autoCompleteBox.SelectedItem = SelectedItem; + //_autoCompleteBox.TextChanged += AutoCompleteBox_TextChanged; + _autoCompleteBox.LostFocus += (sender, args) => + { + if(_autoCompleteBox.SelectedItem == null) + { + _autoCompleteBox.Text = ""; + } + }; + _autoCompleteBox.SelectionChanged += AutoCompleteBox_SelectionChanged; + } + } + + private void AutoCompleteBox_SelectionChanged(object? sender, SelectionChangedEventArgs e) + { + if (_autoCompleteBox?.SelectedItem is string selected) + { + SelectedItem = selected; + } + } + + private void ShowAllOptions_Click(object? sender, RoutedEventArgs e) + { + if (_autoCompleteBox != null) + { + _autoCompleteBox.ItemsSource = new ObservableCollection(Items); + _autoCompleteBox.IsDropDownOpen = true; + } + } + } \ No newline at end of file