mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Merge pull request #657 from Flow-Launcher/JsonRPCPluginSettingControl
Json rpc plugin setting control
This commit is contained in:
commit
f86dd9d287
4 changed files with 291 additions and 13 deletions
|
|
@ -43,15 +43,19 @@ namespace Flow.Launcher.Core.Plugin
|
|||
[JsonPropertyName("result")]
|
||||
public new List<JsonRPCResult> Result { get; set; }
|
||||
|
||||
public Dictionary<string, object> SettingsChange { get; set; }
|
||||
|
||||
public string DebugMessage { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public class JsonRPCRequestModel
|
||||
{
|
||||
public string Method { get; set; }
|
||||
|
||||
public object[] Parameters { get; set; }
|
||||
|
||||
public Dictionary<string, object> Settings { get; set; }
|
||||
|
||||
private static readonly JsonSerializerOptions options = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
|
|
@ -86,5 +90,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
public class JsonRPCResult : Result
|
||||
{
|
||||
public JsonRPCClientRequestModel JsonRPCAction { get; set; }
|
||||
|
||||
public Dictionary<string, object> SettingsChange { get; set; }
|
||||
}
|
||||
}
|
||||
43
Flow.Launcher.Core/Plugin/JsonRPCConfigurationModel.cs
Normal file
43
Flow.Launcher.Core/Plugin/JsonRPCConfigurationModel.cs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Flow.Launcher.Core.Plugin
|
||||
{
|
||||
public class JsonRpcConfigurationModel
|
||||
{
|
||||
public List<SettingField> Body { get; set; }
|
||||
public void Deconstruct(out List<SettingField> 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<string> 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<string> Options, out string DefaultValue)
|
||||
{
|
||||
Name = this.Name;
|
||||
Label = this.Label;
|
||||
Description = this.Description;
|
||||
Validation = this.Validation;
|
||||
Options = this.Options;
|
||||
DefaultValue = this.DefaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,24 @@ using System.Reflection;
|
|||
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 YamlDotNet.Serialization;
|
||||
using YamlDotNet.Serialization.NamingConventions;
|
||||
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;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace Flow.Launcher.Core.Plugin
|
||||
{
|
||||
|
|
@ -21,7 +35,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
/// Represent the plugin that using JsonPRC
|
||||
/// every JsonRPC plugin should has its own plugin instance
|
||||
/// </summary>
|
||||
internal abstract class JsonRPCPlugin : IAsyncPlugin, IContextMenu
|
||||
internal abstract class JsonRPCPlugin : IAsyncPlugin, IContextMenu, ISettingProvider, ISavable
|
||||
{
|
||||
protected PluginInitContext context;
|
||||
public const string JsonRPC = "JsonRPC";
|
||||
|
|
@ -35,6 +49,9 @@ namespace Flow.Launcher.Core.Plugin
|
|||
|
||||
private static readonly RecyclableMemoryStreamManager BufferManager = new();
|
||||
|
||||
private string SettingConfigurationPath => Path.Combine(context.CurrentPluginMetadata.PluginDirectory, "SettingsTemplate.yaml");
|
||||
private string SettingPath => Path.Combine(DataLocation.PluginSettingsDirectory, context.CurrentPluginMetadata.Name, "Settings.json");
|
||||
|
||||
public List<Result> LoadContextMenus(Result selectedResult)
|
||||
{
|
||||
var request = new JsonRPCRequestModel
|
||||
|
|
@ -59,6 +76,14 @@ namespace Flow.Launcher.Core.Plugin
|
|||
}
|
||||
};
|
||||
|
||||
private static readonly JsonSerializerOptions settingSerializeOption = new()
|
||||
{
|
||||
WriteIndented = true
|
||||
};
|
||||
private Dictionary<string, object> Settings { get; set; }
|
||||
|
||||
private Dictionary<string, FrameworkElement> _settingControls = new();
|
||||
|
||||
private async Task<List<Result>> DeserializedResultAsync(Stream output)
|
||||
{
|
||||
if (output == Stream.Null) return null;
|
||||
|
|
@ -92,6 +117,8 @@ namespace Flow.Launcher.Core.Plugin
|
|||
{
|
||||
result.Action = c =>
|
||||
{
|
||||
UpdateSettings(result.SettingsChange);
|
||||
|
||||
if (result.JsonRPCAction == null) return false;
|
||||
|
||||
if (string.IsNullOrEmpty(result.JsonRPCAction.Method))
|
||||
|
|
@ -131,6 +158,8 @@ namespace Flow.Launcher.Core.Plugin
|
|||
|
||||
results.AddRange(queryResponseModel.Result);
|
||||
|
||||
UpdateSettings(queryResponseModel.SettingsChange);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
|
|
@ -283,19 +312,222 @@ namespace Flow.Launcher.Core.Plugin
|
|||
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);
|
||||
}
|
||||
|
||||
public virtual Task InitAsync(PluginInitContext context)
|
||||
public async Task InitSettingAsync()
|
||||
{
|
||||
if (!File.Exists(SettingConfigurationPath))
|
||||
return;
|
||||
|
||||
if (File.Exists(SettingPath))
|
||||
{
|
||||
await using var fileStream = File.OpenRead(SettingPath);
|
||||
Settings = await JsonSerializer.DeserializeAsync<Dictionary<string, object>>(fileStream, options);
|
||||
}
|
||||
|
||||
var deserializer = new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).Build();
|
||||
_settingsTemplate = deserializer.Deserialize<JsonRpcConfigurationModel>(await File.ReadAllTextAsync(SettingConfigurationPath));
|
||||
|
||||
Settings ??= new Dictionary<string, object>();
|
||||
|
||||
foreach (var (type, attribute) in _settingsTemplate.Body)
|
||||
{
|
||||
if (type == "textBlock")
|
||||
continue;
|
||||
if (!Settings.ContainsKey(attribute.Name))
|
||||
{
|
||||
Settings[attribute.Name] = attribute.DefaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual async Task InitAsync(PluginInitContext context)
|
||||
{
|
||||
this.context = context;
|
||||
return Task.CompletedTask;
|
||||
await InitSettingAsync();
|
||||
}
|
||||
private static readonly Thickness settingControlMargin = new(10);
|
||||
private JsonRpcConfigurationModel _settingsTemplate;
|
||||
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 (type, attribute) in _settingsTemplate.Body)
|
||||
{
|
||||
var panel = new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Horizontal,
|
||||
Margin = settingControlMargin
|
||||
};
|
||||
var name = new Label()
|
||||
{
|
||||
Content = attribute.Label,
|
||||
Margin = settingControlMargin
|
||||
};
|
||||
|
||||
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,
|
||||
ToolTip = attribute.Description
|
||||
};
|
||||
textBox.TextChanged += (_, _) =>
|
||||
{
|
||||
Settings[attribute.Name] = textBox.Text;
|
||||
};
|
||||
contentControl = textBox;
|
||||
break;
|
||||
}
|
||||
case "textarea":
|
||||
{
|
||||
var textBox = new TextBox()
|
||||
{
|
||||
Width = 300,
|
||||
Height = 120,
|
||||
Margin = settingControlMargin,
|
||||
TextWrapping = TextWrapping.WrapWithOverflow,
|
||||
AcceptsReturn = true,
|
||||
Text = Settings[attribute.Name] as string ?? string.Empty,
|
||||
ToolTip = attribute.Description
|
||||
};
|
||||
textBox.TextChanged += (sender, _) =>
|
||||
{
|
||||
Settings[attribute.Name] = ((TextBox)sender).Text;
|
||||
};
|
||||
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,
|
||||
ToolTip = attribute.Description
|
||||
};
|
||||
passwordBox.PasswordChanged += (sender, _) =>
|
||||
{
|
||||
Settings[attribute.Name] = ((PasswordBox)sender).Password;
|
||||
};
|
||||
contentControl = passwordBox;
|
||||
break;
|
||||
}
|
||||
case "dropdown":
|
||||
{
|
||||
var comboBox = new ComboBox()
|
||||
{
|
||||
ItemsSource = attribute.Options,
|
||||
SelectedItem = Settings[attribute.Name],
|
||||
Margin = settingControlMargin,
|
||||
ToolTip = attribute.Description
|
||||
};
|
||||
comboBox.SelectionChanged += (sender, _) =>
|
||||
{
|
||||
Settings[attribute.Name] = (string)((ComboBox)sender).SelectedItem;
|
||||
};
|
||||
contentControl = comboBox;
|
||||
break;
|
||||
}
|
||||
case "checkbox":
|
||||
var checkBox = new CheckBox
|
||||
{
|
||||
IsChecked = Settings[attribute.Name] is bool isChecked ? isChecked : bool.Parse(attribute.DefaultValue),
|
||||
Margin = settingControlMargin,
|
||||
ToolTip = attribute.Description
|
||||
};
|
||||
checkBox.Click += (sender, _) =>
|
||||
{
|
||||
Settings[attribute.Name] = ((CheckBox)sender).IsChecked;
|
||||
};
|
||||
contentControl = checkBox;
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
if (type != "textBlock")
|
||||
_settingControls[attribute.Name] = contentControl;
|
||||
panel.Children.Add(name);
|
||||
panel.Children.Add(contentControl);
|
||||
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, settingSerializeOption));
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateSettings(Dictionary<string, object> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -51,15 +51,12 @@ namespace Flow.Launcher.Core.Plugin
|
|||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue