From 67027eb74b392380e588e00a1e801394bd6514bd Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Tue, 17 Aug 2021 00:50:36 +0800 Subject: [PATCH 01/12] Allow JsonRPCPlugin.cs to have setting control --- Flow.Launcher.Core/Plugin/JsonPRCModel.cs | 2 +- Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs | 130 ++++++++++++++++++++- Flow.Launcher.Core/Plugin/PythonPlugin.cs | 7 +- 3 files changed, 128 insertions(+), 11 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/JsonPRCModel.cs b/Flow.Launcher.Core/Plugin/JsonPRCModel.cs index 5232e46daee..3dcefc094ee 100644 --- a/Flow.Launcher.Core/Plugin/JsonPRCModel.cs +++ b/Flow.Launcher.Core/Plugin/JsonPRCModel.cs @@ -45,7 +45,7 @@ public class JsonRPCQueryResponseModel : JsonRPCResponseModel public string DebugMessage { get; set; } } - + public class JsonRPCRequestModel { public string Method { get; set; } diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs index 65977219d8f..d1bfaee216a 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs @@ -1,4 +1,6 @@ -using Flow.Launcher.Core.Resource; +using Accessibility; +using Flow.Launcher.Core.Resource; +using Flow.Launcher.Infrastructure; using System; using System.Collections.Generic; using System.Diagnostics; @@ -8,12 +10,22 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using System.Windows.Forms; using Flow.Launcher.Infrastructure.Logger; +using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; using ICSharpCode.SharpZipLib.Zip; using JetBrains.Annotations; using Microsoft.IO; +using System.Text.Json.Serialization; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Forms; +using CheckBox = System.Windows.Controls.CheckBox; +using Control = System.Windows.Controls.Control; +using Label = System.Windows.Controls.Label; +using Orientation = System.Windows.Controls.Orientation; +using TextBox = System.Windows.Controls.TextBox; +using UserControl = System.Windows.Controls.UserControl; namespace Flow.Launcher.Core.Plugin { @@ -21,7 +33,7 @@ namespace Flow.Launcher.Core.Plugin /// Represent the plugin that using JsonPRC /// every JsonRPC plugin should has its own plugin instance /// - internal abstract class JsonRPCPlugin : IAsyncPlugin, IContextMenu + internal abstract class JsonRPCPlugin : IAsyncPlugin, IContextMenu, ISettingProvider, ISavable { protected PluginInitContext context; public const string JsonRPC = "JsonRPC"; @@ -35,6 +47,8 @@ internal abstract class JsonRPCPlugin : IAsyncPlugin, IContextMenu private static readonly RecyclableMemoryStreamManager BufferManager = new(); + private string SettingPath => Path.Combine(DataLocation.PluginSettingsDirectory, context.CurrentPluginMetadata.Name, "Setting.json"); + public List LoadContextMenus(Result selectedResult) { var request = new JsonRPCRequestModel @@ -58,6 +72,7 @@ public List LoadContextMenus(Result selectedResult) new JsonObjectConverter() } }; + private Dictionary Settings { get; set; } private async Task> DeserializedResultAsync(Stream output) { @@ -292,10 +307,115 @@ public async Task> QueryAsync(Query query, CancellationToken token) return await DeserializedResultAsync(output); } - public virtual Task InitAsync(PluginInitContext context) + public async Task InitSettingAsync() + { + if (File.Exists(SettingPath)) + Settings = await JsonSerializer.DeserializeAsync>(File.OpenRead(SettingPath), options); + + var request = new JsonRPCRequestModel() + { + Method = "get_setting_template" + }; + await using var result = await RequestAsync(request); + if (result.Length == 0) + return; + var settingsTemplate = await JsonSerializer.DeserializeAsync>(result, options) ?? + new(); + + Settings ??= new(); + + foreach (var (key, element) in settingsTemplate) + { + if (!Settings.ContainsKey(key)) + { + Settings[key] = element.ValueKind switch + { + JsonValueKind.True or JsonValueKind.False => element.GetBoolean(), + JsonValueKind.String or JsonValueKind.Number => element.GetString(), + JsonValueKind.Null => throw new ArgumentNullException(), + _ => throw new ArgumentOutOfRangeException() + }; + } + } + } + + public virtual async Task InitAsync(PluginInitContext context) { this.context = context; - return Task.CompletedTask; + await InitSettingAsync(); + } + private static Thickness settingControlMargin = new(10); + public Control CreateSettingPanel() + { + if (Settings == null) + return new(); + var settingWindow = new UserControl(); + var mainPanel = new StackPanel + { + Margin = settingControlMargin, + Orientation = Orientation.Vertical + }; + settingWindow.Content = mainPanel; + foreach (var (key, value) in Settings) + { + var panel = new StackPanel + { + Orientation = Orientation.Horizontal, + Margin = settingControlMargin + }; + var name = new Label + { + Content = key, + VerticalAlignment = VerticalAlignment.Center + }; + UIElement content = null; + switch (value) + { + case int i: + case double d: + throw new TypeAccessException(); + case string s: + var textBox = new TextBox + { + Text = s, + Margin = settingControlMargin, + VerticalAlignment = VerticalAlignment.Center + }; + textBox.TextChanged += (_, _) => + { + Settings[key] = textBox.Text; + }; + content = textBox; + break; + case bool b: + var checkBox = new CheckBox + { + IsChecked = b, + Margin = settingControlMargin, + VerticalAlignment = VerticalAlignment.Center + }; + checkBox.Click += (_, _) => + { + Settings[key] = checkBox.IsChecked; + }; + content = checkBox; + break; + default: + throw new ArgumentOutOfRangeException(); + } + panel.Children.Add(name); + panel.Children.Add(content); + mainPanel.Children.Add(panel); + } + return settingWindow; + } + public void Save() + { + if (Settings != null) + { + Helper.ValidateDirectory(Path.Combine(DataLocation.PluginSettingsDirectory, context.CurrentPluginMetadata.Name)); + File.WriteAllText(SettingPath, JsonSerializer.Serialize(Settings)); + } } } } \ No newline at end of file diff --git a/Flow.Launcher.Core/Plugin/PythonPlugin.cs b/Flow.Launcher.Core/Plugin/PythonPlugin.cs index 5711ed6aae9..968c0ab2325 100644 --- a/Flow.Launcher.Core/Plugin/PythonPlugin.cs +++ b/Flow.Launcher.Core/Plugin/PythonPlugin.cs @@ -46,15 +46,12 @@ protected override string Request(JsonRPCRequestModel rpcRequest, CancellationTo // TODO: Async Action return Execute(_startInfo); } - public override Task InitAsync(PluginInitContext context) + public override async Task InitAsync(PluginInitContext context) { - this.context = context; _startInfo.ArgumentList.Add(context.CurrentPluginMetadata.ExecuteFilePath); _startInfo.ArgumentList.Add(""); - + await base.InitAsync(context); _startInfo.WorkingDirectory = context.CurrentPluginMetadata.PluginDirectory; - - return Task.CompletedTask; } } } \ No newline at end of file From 054d1650e64f809078eede5cd14ec9338e18eeae Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Sat, 30 Oct 2021 13:46:57 -0500 Subject: [PATCH 02/12] Use Yaml as configuration file --- .../Plugin/JsonRPCConfigurationModel.cs | 42 +++++ Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs | 173 ++++++++++++------ 2 files changed, 164 insertions(+), 51 deletions(-) create mode 100644 Flow.Launcher.Core/Plugin/JsonRPCConfigurationModel.cs diff --git a/Flow.Launcher.Core/Plugin/JsonRPCConfigurationModel.cs b/Flow.Launcher.Core/Plugin/JsonRPCConfigurationModel.cs new file mode 100644 index 00000000000..7eb5ab9c3c7 --- /dev/null +++ b/Flow.Launcher.Core/Plugin/JsonRPCConfigurationModel.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; + +namespace Flow.Launcher.Core.Plugin +{ + public class JsonRpcConfigurationModel + { + public List Body { get; set; } + public void Deconstruct(out List Body) + { + Body = this.Body; + } + } + + public class SettingField + { + public string Type { get; set; } + public FieldAttributes Attributes { get; set; } + public void Deconstruct(out string Type, out FieldAttributes attributes) + { + Type = this.Type; + attributes = this.Attributes; + } + } + public class FieldAttributes + { + public string Name { get; set; } + public string Label { get; set; } + public string Description { get; set; } + public bool Validation { get; set; } + public List Options { get; set; } + public string DefaultValue { get; set; } + public void Deconstruct(out string Name, out string Label, out string Description, out bool Validation, out List Options, out string DefaultValue) + { + Name = this.Name; + Label = this.Label; + Description = this.Description; + Validation = this.Validation; + Options = this.Options; + DefaultValue = this.DefaultValue; + } + } +} \ No newline at end of file diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs index d1bfaee216a..ce5e8ed7f99 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs @@ -19,7 +19,8 @@ using System.Text.Json.Serialization; using System.Windows; using System.Windows.Controls; -using System.Windows.Forms; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; using CheckBox = System.Windows.Controls.CheckBox; using Control = System.Windows.Controls.Control; using Label = System.Windows.Controls.Label; @@ -47,6 +48,7 @@ internal abstract class JsonRPCPlugin : IAsyncPlugin, IContextMenu, ISettingProv private static readonly RecyclableMemoryStreamManager BufferManager = new(); + private string SettingConfigurationPath => Path.Combine(context.CurrentPluginMetadata.PluginDirectory, "SettingConfiguration.yaml"); private string SettingPath => Path.Combine(DataLocation.PluginSettingsDirectory, context.CurrentPluginMetadata.Name, "Setting.json"); public List LoadContextMenus(Result selectedResult) @@ -309,32 +311,22 @@ public async Task> QueryAsync(Query query, CancellationToken token) public async Task InitSettingAsync() { + if (!File.Exists(SettingConfigurationPath)) + return; + if (File.Exists(SettingPath)) Settings = await JsonSerializer.DeserializeAsync>(File.OpenRead(SettingPath), options); - var request = new JsonRPCRequestModel() - { - Method = "get_setting_template" - }; - await using var result = await RequestAsync(request); - if (result.Length == 0) - return; - var settingsTemplate = await JsonSerializer.DeserializeAsync>(result, options) ?? - new(); + var deserializer = new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).Build(); + _settingsTemplate = deserializer.Deserialize(await File.ReadAllTextAsync(SettingConfigurationPath)); - Settings ??= new(); + Settings ??= new Dictionary(); - foreach (var (key, element) in settingsTemplate) + foreach (var (type, attribute) in _settingsTemplate.Body) { - if (!Settings.ContainsKey(key)) + if (!Settings.ContainsKey(attribute.Name)) { - Settings[key] = element.ValueKind switch - { - JsonValueKind.True or JsonValueKind.False => element.GetBoolean(), - JsonValueKind.String or JsonValueKind.Number => element.GetString(), - JsonValueKind.Null => throw new ArgumentNullException(), - _ => throw new ArgumentOutOfRangeException() - }; + Settings[attribute.Name] = attribute.DefaultValue; } } } @@ -344,7 +336,8 @@ public virtual async Task InitAsync(PluginInitContext context) this.context = context; await InitSettingAsync(); } - private static Thickness settingControlMargin = new(10); + private static readonly Thickness settingControlMargin = new(10); + private JsonRpcConfigurationModel _settingsTemplate; public Control CreateSettingPanel() { if (Settings == null) @@ -352,61 +345,139 @@ public Control CreateSettingPanel() var settingWindow = new UserControl(); var mainPanel = new StackPanel { - Margin = settingControlMargin, - Orientation = Orientation.Vertical + Margin = settingControlMargin, Orientation = Orientation.Vertical }; settingWindow.Content = mainPanel; - foreach (var (key, value) in Settings) + + foreach (var (type, attribute) in _settingsTemplate.Body) { var panel = new StackPanel { Orientation = Orientation.Horizontal, Margin = settingControlMargin }; - var name = new Label + var name = new Label() { - Content = key, - VerticalAlignment = VerticalAlignment.Center + Content = attribute.Label, + Margin = settingControlMargin }; - UIElement content = null; - switch (value) + + Control contentControl; + + switch (type) { - case int i: - case double d: - throw new TypeAccessException(); - case string s: - var textBox = new TextBox + case "Input": { - Text = s, - Margin = settingControlMargin, - VerticalAlignment = VerticalAlignment.Center - }; - textBox.TextChanged += (_, _) => + var textBox = new TextBox() + { + Width = 300, Text = Settings[attribute.Name] as string ?? string.Empty, + Margin = settingControlMargin + }; + textBox.TextChanged += (_, _) => + { + Settings[attribute.Name] = textBox.Text; + }; + contentControl = textBox; + break; + } + case "textarea": { - Settings[key] = textBox.Text; - }; - content = textBox; - break; - case bool b: + var textBox = new TextBox() + { + Width = 300, + Height = 100, + Margin = settingControlMargin, + TextWrapping = TextWrapping.WrapWithOverflow, + Text = Settings[attribute.Name] as string ?? string.Empty + }; + textBox.TextChanged += (sender, _) => + { + Settings[attribute.Name] = ((TextBox)sender).Text; + }; + contentControl = textBox; + break; + } + case "dropdown": + { + var comboBox = new ComboBox() + { + ItemsSource = attribute.Options, SelectedItem = Settings[attribute.Name], + Margin = settingControlMargin + }; + comboBox.SelectionChanged += (sender, _) => + { + Settings[attribute.Name] = (string)((ComboBox)sender).SelectedItem; + }; + contentControl = comboBox; + break; + } + case "checkbox": var checkBox = new CheckBox { - IsChecked = b, - Margin = settingControlMargin, - VerticalAlignment = VerticalAlignment.Center + IsChecked = Settings[attribute.Name] is bool isChecked ? isChecked : bool.Parse(attribute.DefaultValue), + Margin = settingControlMargin }; checkBox.Click += (_, _) => { - Settings[key] = checkBox.IsChecked; + Settings[attribute.Name] = !((bool)Settings[attribute.Name]); }; - content = checkBox; + contentControl = checkBox; break; default: - throw new ArgumentOutOfRangeException(); + continue; } panel.Children.Add(name); - panel.Children.Add(content); + panel.Children.Add(contentControl); mainPanel.Children.Add(panel); } + + // foreach (var (key, value) in Settings) + // { + // var panel = new StackPanel + // { + // Orientation = Orientation.Horizontal, Margin = settingControlMargin + // }; + // var name = new Label + // { + // Content = key, VerticalAlignment = VerticalAlignment.Center + // }; + // UIElement content = null; + // switch (value) + // { + // case int i: + // case double d: + // throw new TypeAccessException(); + // case string s: + // var textBox = new TextBox + // { + // Text = s, + // Margin = settingControlMargin, + // VerticalAlignment = VerticalAlignment.Center + // }; + // textBox.TextChanged += (_, _) => + // { + // Settings[key] = textBox.Text; + // }; + // content = textBox; + // break; + // case bool b: + // var checkBox = new CheckBox + // { + // IsChecked = b, + // Margin = settingControlMargin, + // VerticalAlignment = VerticalAlignment.Center + // }; + // checkBox.Click += (_, _) => + // { + // Settings[key] = checkBox.IsChecked; + // }; + // content = checkBox; + // break; + // default: + // throw new ArgumentOutOfRangeException(); + // } + // + // } return settingWindow; } public void Save() From 9cd3f90ec43353b2f60becf98889f056bc65b132 Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Sat, 30 Oct 2021 16:17:33 -0500 Subject: [PATCH 03/12] Change some configuration and refactor code --- Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs index ce5e8ed7f99..358f2956f54 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs @@ -48,8 +48,8 @@ internal abstract class JsonRPCPlugin : IAsyncPlugin, IContextMenu, ISettingProv private static readonly RecyclableMemoryStreamManager BufferManager = new(); - private string SettingConfigurationPath => Path.Combine(context.CurrentPluginMetadata.PluginDirectory, "SettingConfiguration.yaml"); - private string SettingPath => Path.Combine(DataLocation.PluginSettingsDirectory, context.CurrentPluginMetadata.Name, "Setting.json"); + private string SettingConfigurationPath => Path.Combine(context.CurrentPluginMetadata.PluginDirectory, "SettingsTemplate.yaml"); + private string SettingPath => Path.Combine(DataLocation.PluginSettingsDirectory, context.CurrentPluginMetadata.Name, "Settings.json"); public List LoadContextMenus(Result selectedResult) { @@ -417,9 +417,9 @@ public Control CreateSettingPanel() IsChecked = Settings[attribute.Name] is bool isChecked ? isChecked : bool.Parse(attribute.DefaultValue), Margin = settingControlMargin }; - checkBox.Click += (_, _) => + checkBox.Click += (sender, _) => { - Settings[attribute.Name] = !((bool)Settings[attribute.Name]); + Settings[attribute.Name] = ((CheckBox) sender).IsChecked; }; contentControl = checkBox; break; From 05104a5ab0f0f529f59f298b58f9a761002e5e0b Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Sat, 13 Nov 2021 23:11:05 -0600 Subject: [PATCH 04/12] Implement Setting back and forward transfer --- Flow.Launcher.Core/Plugin/JsonPRCModel.cs | 4 ++++ Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs | 24 ++++++++++++++++------ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/JsonPRCModel.cs b/Flow.Launcher.Core/Plugin/JsonPRCModel.cs index 3dcefc094ee..4ec25a8434a 100644 --- a/Flow.Launcher.Core/Plugin/JsonPRCModel.cs +++ b/Flow.Launcher.Core/Plugin/JsonPRCModel.cs @@ -43,6 +43,8 @@ public class JsonRPCQueryResponseModel : JsonRPCResponseModel [JsonPropertyName("result")] public new List Result { get; set; } + public Dictionary SettingsChange { get; set; } + public string DebugMessage { get; set; } } @@ -52,6 +54,8 @@ public class JsonRPCRequestModel public object[] Parameters { get; set; } + public Dictionary Settings { get; set; } + private static readonly JsonSerializerOptions options = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs index 358f2956f54..255499efdf4 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs @@ -148,6 +148,14 @@ private List ParseResults(JsonRPCQueryResponseModel queryResponseModel) results.AddRange(queryResponseModel.Result); + if (queryResponseModel.SettingsChange != null) + { + foreach (var (key, value) in queryResponseModel.SettingsChange) + { + Settings[key] = value; + } + } + return results; } @@ -300,10 +308,11 @@ public async Task> QueryAsync(Query query, CancellationToken token) var request = new JsonRPCRequestModel { Method = "query", - Parameters = new[] + Parameters = new object[] { query.Search - } + }, + Settings = Settings }; var output = await RequestAsync(request, token); return await DeserializedResultAsync(output); @@ -345,7 +354,8 @@ public Control CreateSettingPanel() var settingWindow = new UserControl(); var mainPanel = new StackPanel { - Margin = settingControlMargin, Orientation = Orientation.Vertical + Margin = settingControlMargin, + Orientation = Orientation.Vertical }; settingWindow.Content = mainPanel; @@ -370,7 +380,8 @@ public Control CreateSettingPanel() { var textBox = new TextBox() { - Width = 300, Text = Settings[attribute.Name] as string ?? string.Empty, + Width = 300, + Text = Settings[attribute.Name] as string ?? string.Empty, Margin = settingControlMargin }; textBox.TextChanged += (_, _) => @@ -401,7 +412,8 @@ public Control CreateSettingPanel() { var comboBox = new ComboBox() { - ItemsSource = attribute.Options, SelectedItem = Settings[attribute.Name], + ItemsSource = attribute.Options, + SelectedItem = Settings[attribute.Name], Margin = settingControlMargin }; comboBox.SelectionChanged += (sender, _) => @@ -419,7 +431,7 @@ public Control CreateSettingPanel() }; checkBox.Click += (sender, _) => { - Settings[attribute.Name] = ((CheckBox) sender).IsChecked; + Settings[attribute.Name] = ((CheckBox)sender).IsChecked; }; contentControl = checkBox; break; From ddc6af52ed4fba8d1e7779c52c4a127e7ff5d964 Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Sat, 13 Nov 2021 23:48:53 -0600 Subject: [PATCH 05/12] Allow modifying Setting via result --- Flow.Launcher.Core/Plugin/JsonPRCModel.cs | 2 + Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs | 56 ++++------------------ 2 files changed, 10 insertions(+), 48 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/JsonPRCModel.cs b/Flow.Launcher.Core/Plugin/JsonPRCModel.cs index 4ec25a8434a..cf75e4aa3e3 100644 --- a/Flow.Launcher.Core/Plugin/JsonPRCModel.cs +++ b/Flow.Launcher.Core/Plugin/JsonPRCModel.cs @@ -90,5 +90,7 @@ public class JsonRPCClientRequestModel : JsonRPCRequestModel public class JsonRPCResult : Result { public JsonRPCClientRequestModel JsonRPCAction { get; set; } + + public Dictionary SettingsChange { get; set; } } } \ No newline at end of file diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs index 255499efdf4..fb8c0c4f063 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs @@ -109,6 +109,14 @@ private List ParseResults(JsonRPCQueryResponseModel queryResponseModel) { result.Action = c => { + if (result.SettingsChange is not null) + { + foreach (var (key, value) in result.SettingsChange) + { + Settings[key] = value; + } + } + if (result.JsonRPCAction == null) return false; if (string.IsNullOrEmpty(result.JsonRPCAction.Method)) @@ -442,54 +450,6 @@ public Control CreateSettingPanel() panel.Children.Add(contentControl); mainPanel.Children.Add(panel); } - - // foreach (var (key, value) in Settings) - // { - // var panel = new StackPanel - // { - // Orientation = Orientation.Horizontal, Margin = settingControlMargin - // }; - // var name = new Label - // { - // Content = key, VerticalAlignment = VerticalAlignment.Center - // }; - // UIElement content = null; - // switch (value) - // { - // case int i: - // case double d: - // throw new TypeAccessException(); - // case string s: - // var textBox = new TextBox - // { - // Text = s, - // Margin = settingControlMargin, - // VerticalAlignment = VerticalAlignment.Center - // }; - // textBox.TextChanged += (_, _) => - // { - // Settings[key] = textBox.Text; - // }; - // content = textBox; - // break; - // case bool b: - // var checkBox = new CheckBox - // { - // IsChecked = b, - // Margin = settingControlMargin, - // VerticalAlignment = VerticalAlignment.Center - // }; - // checkBox.Click += (_, _) => - // { - // Settings[key] = checkBox.IsChecked; - // }; - // content = checkBox; - // break; - // default: - // throw new ArgumentOutOfRangeException(); - // } - // - // } return settingWindow; } public void Save() From 36bb4396474159893b3fead99acab339142903e7 Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Sat, 13 Nov 2021 23:59:14 -0600 Subject: [PATCH 06/12] Add passwordBox --- .../Plugin/JsonRPCConfigurationModel.cs | 1 + Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/Flow.Launcher.Core/Plugin/JsonRPCConfigurationModel.cs b/Flow.Launcher.Core/Plugin/JsonRPCConfigurationModel.cs index 7eb5ab9c3c7..1f63f85a806 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCConfigurationModel.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCConfigurationModel.cs @@ -29,6 +29,7 @@ public class FieldAttributes public bool Validation { get; set; } public List Options { get; set; } public string DefaultValue { get; set; } + public char passwordChar { get; set; } public void Deconstruct(out string Name, out string Label, out string Description, out bool Validation, out List Options, out string DefaultValue) { Name = this.Name; diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs index fb8c0c4f063..c488bb8d09b 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs @@ -416,6 +416,17 @@ public Control CreateSettingPanel() contentControl = textBox; break; } + case "passwordBox": + { + var passwordBox = new PasswordBox() + { + Width = 300, + Margin = settingControlMargin, + Password = Settings[attribute.Name] as string ?? string.Empty, + PasswordChar = attribute.passwordChar == default ? '*' : attribute.passwordChar + }; + break; + } case "dropdown": { var comboBox = new ComboBox() From 420d8ea0053f1ce0a2cb4df94f2866825facedcf Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Sun, 14 Nov 2021 11:12:47 -0600 Subject: [PATCH 07/12] fix passwordBox issue --- Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs index c488bb8d09b..0819ded847b 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs @@ -425,6 +425,11 @@ public Control CreateSettingPanel() Password = Settings[attribute.Name] as string ?? string.Empty, PasswordChar = attribute.passwordChar == default ? '*' : attribute.passwordChar }; + passwordBox.PasswordChanged += (sender, _) => + { + Settings[attribute.Name] = ((PasswordBox)sender).Password; + }; + contentControl = passwordBox; break; } case "dropdown": From c677a636797584d8ba9173f89a6481d8cfaeea37 Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Sun, 14 Nov 2021 11:46:09 -0600 Subject: [PATCH 08/12] Manually Implement UI Binding --- Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs | 62 ++++++++++++++++------ 1 file changed, 46 insertions(+), 16 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs index 0819ded847b..6d1d15d6c0c 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs @@ -27,6 +27,7 @@ using Orientation = System.Windows.Controls.Orientation; using TextBox = System.Windows.Controls.TextBox; using UserControl = System.Windows.Controls.UserControl; +using System.Windows.Data; namespace Flow.Launcher.Core.Plugin { @@ -74,8 +75,15 @@ public List LoadContextMenus(Result selectedResult) new JsonObjectConverter() } }; + + private static readonly JsonSerializerOptions settingSerializeOption = new() + { + WriteIndented = true + }; private Dictionary Settings { get; set; } + private Dictionary _settingControls = new(); + private async Task> DeserializedResultAsync(Stream output) { if (output == Stream.Null) return null; @@ -109,13 +117,7 @@ private List ParseResults(JsonRPCQueryResponseModel queryResponseModel) { result.Action = c => { - if (result.SettingsChange is not null) - { - foreach (var (key, value) in result.SettingsChange) - { - Settings[key] = value; - } - } + UpdateSettings(result.SettingsChange); if (result.JsonRPCAction == null) return false; @@ -156,13 +158,7 @@ private List ParseResults(JsonRPCQueryResponseModel queryResponseModel) results.AddRange(queryResponseModel.Result); - if (queryResponseModel.SettingsChange != null) - { - foreach (var (key, value) in queryResponseModel.SettingsChange) - { - Settings[key] = value; - } - } + UpdateSettings(queryResponseModel.SettingsChange); return results; } @@ -384,7 +380,7 @@ public Control CreateSettingPanel() switch (type) { - case "Input": + case "input": { var textBox = new TextBox() { @@ -462,6 +458,7 @@ public Control CreateSettingPanel() default: continue; } + _settingControls[attribute.Name] = contentControl; panel.Children.Add(name); panel.Children.Add(contentControl); mainPanel.Children.Add(panel); @@ -473,7 +470,40 @@ public void Save() if (Settings != null) { Helper.ValidateDirectory(Path.Combine(DataLocation.PluginSettingsDirectory, context.CurrentPluginMetadata.Name)); - File.WriteAllText(SettingPath, JsonSerializer.Serialize(Settings)); + File.WriteAllText(SettingPath, JsonSerializer.Serialize(Settings, settingSerializeOption)); + } + } + + public void UpdateSettings(Dictionary settings) + { + if (settings == null || settings.Count == 0) + return; + + foreach (var (key, value) in settings) + { + if (Settings.ContainsKey(key)) + { + Settings[key] = value; + } + if (_settingControls.ContainsKey(key)) + { + + switch (_settingControls[key]) + { + case TextBox textBox: + textBox.Dispatcher.Invoke(() => textBox.Text = value as string); + break; + case PasswordBox passwordBox: + passwordBox.Dispatcher.Invoke(() => passwordBox.Password = value as string); + break; + case ComboBox comboBox: + comboBox.Dispatcher.Invoke(() => comboBox.SelectedItem = value); + break; + case CheckBox checkBox: + checkBox.Dispatcher.Invoke(() => checkBox.IsChecked = value is bool isChecked ? isChecked : bool.Parse(value as string)); + break; + } + } } } } From c35ddbc2d91275aab52b6709d1c44f446c41fa5a Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Sun, 14 Nov 2021 21:19:32 -0600 Subject: [PATCH 09/12] add ability to add new line in textarea --- Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs index 6d1d15d6c0c..7b28fa77c1d 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs @@ -400,9 +400,10 @@ public Control CreateSettingPanel() var textBox = new TextBox() { Width = 300, - Height = 100, + Height = 120, Margin = settingControlMargin, TextWrapping = TextWrapping.WrapWithOverflow, + AcceptsReturn = true, Text = Settings[attribute.Name] as string ?? string.Empty }; textBox.TextChanged += (sender, _) => From 0a3b566e56b109aebbb3e6b5e5dbac5e6c2e5fd9 Mon Sep 17 00:00:00 2001 From: Kevin Zhang Date: Mon, 15 Nov 2021 20:35:05 -0600 Subject: [PATCH 10/12] Implment textBlock and add tooltip --- Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs | 37 ++++++++++++++++------ 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs index 7b28fa77c1d..624c58dec0b 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs @@ -40,7 +40,7 @@ internal abstract class JsonRPCPlugin : IAsyncPlugin, IContextMenu, ISettingProv protected PluginInitContext context; public const string JsonRPC = "JsonRPC"; - /// + /// public abstract string SupportedLanguage { get; set; } @@ -82,7 +82,7 @@ public List LoadContextMenus(Result selectedResult) }; private Dictionary Settings { get; set; } - private Dictionary _settingControls = new(); + private Dictionary _settingControls = new(); private async Task> DeserializedResultAsync(Stream output) { @@ -337,6 +337,8 @@ public async Task InitSettingAsync() foreach (var (type, attribute) in _settingsTemplate.Body) { + if (type == "textBlock") + continue; if (!Settings.ContainsKey(attribute.Name)) { Settings[attribute.Name] = attribute.DefaultValue; @@ -376,17 +378,29 @@ public Control CreateSettingPanel() Margin = settingControlMargin }; - Control contentControl; + FrameworkElement contentControl; switch (type) { + case "textBlock": + { + contentControl = new TextBlock + { + Text = attribute.Description.Replace("\\r\\n", "\r\n"), + Margin = settingControlMargin, + MaxWidth = 400, + TextWrapping = TextWrapping.WrapWithOverflow + }; + break; + } case "input": { var textBox = new TextBox() { Width = 300, Text = Settings[attribute.Name] as string ?? string.Empty, - Margin = settingControlMargin + Margin = settingControlMargin, + ToolTip = attribute.Description }; textBox.TextChanged += (_, _) => { @@ -404,7 +418,8 @@ public Control CreateSettingPanel() Margin = settingControlMargin, TextWrapping = TextWrapping.WrapWithOverflow, AcceptsReturn = true, - Text = Settings[attribute.Name] as string ?? string.Empty + Text = Settings[attribute.Name] as string ?? string.Empty, + ToolTip = attribute.Description }; textBox.TextChanged += (sender, _) => { @@ -420,7 +435,8 @@ public Control CreateSettingPanel() Width = 300, Margin = settingControlMargin, Password = Settings[attribute.Name] as string ?? string.Empty, - PasswordChar = attribute.passwordChar == default ? '*' : attribute.passwordChar + PasswordChar = attribute.passwordChar == default ? '*' : attribute.passwordChar, + ToolTip = attribute.Description }; passwordBox.PasswordChanged += (sender, _) => { @@ -435,7 +451,8 @@ public Control CreateSettingPanel() { ItemsSource = attribute.Options, SelectedItem = Settings[attribute.Name], - Margin = settingControlMargin + Margin = settingControlMargin, + ToolTip = attribute.Description }; comboBox.SelectionChanged += (sender, _) => { @@ -448,7 +465,8 @@ public Control CreateSettingPanel() var checkBox = new CheckBox { IsChecked = Settings[attribute.Name] is bool isChecked ? isChecked : bool.Parse(attribute.DefaultValue), - Margin = settingControlMargin + Margin = settingControlMargin, + ToolTip = attribute.Description }; checkBox.Click += (sender, _) => { @@ -459,7 +477,8 @@ public Control CreateSettingPanel() default: continue; } - _settingControls[attribute.Name] = contentControl; + if (type != "textBlock") + _settingControls[attribute.Name] = contentControl; panel.Children.Add(name); panel.Children.Add(contentControl); mainPanel.Children.Add(panel); From 5ea8675c0262a7a69578519f8fa60f99a0989c38 Mon Sep 17 00:00:00 2001 From: Kevin Zhang <45326534+taooceros@users.noreply.github.com> Date: Wed, 8 Dec 2021 16:20:54 -0600 Subject: [PATCH 11/12] Update Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs Co-authored-by: Jeremy Wu --- Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs index 624c58dec0b..cde21507ec7 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs @@ -40,7 +40,7 @@ internal abstract class JsonRPCPlugin : IAsyncPlugin, IContextMenu, ISettingProv protected PluginInitContext context; public const string JsonRPC = "JsonRPC"; - /// /// The language this JsonRPCPlugin support /// public abstract string SupportedLanguage { get; set; } From 238d4df1097b23a6256a15705c1a4eab5530df0a Mon Sep 17 00:00:00 2001 From: Hongtao Zhang Date: Wed, 8 Dec 2021 21:17:51 -0600 Subject: [PATCH 12/12] Add using for File.OpenRead --- Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs index cde21507ec7..384418db974 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs @@ -328,7 +328,10 @@ public async Task InitSettingAsync() return; if (File.Exists(SettingPath)) - Settings = await JsonSerializer.DeserializeAsync>(File.OpenRead(SettingPath), options); + { + await using var fileStream = File.OpenRead(SettingPath); + Settings = await JsonSerializer.DeserializeAsync>(fileStream, options); + } var deserializer = new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).Build(); _settingsTemplate = deserializer.Deserialize(await File.ReadAllTextAsync(SettingConfigurationPath));