mirror of
https://github.com/Stone-Red-Code/DesktopMagic.git
synced 2026-09-04 00:46:12 +02:00
Add web plugin creation flow and settings bridge to WebPluginWindow
This commit is contained in:
@@ -16,9 +16,9 @@
|
||||
WindowCornerPreference="Round"
|
||||
WindowBackdropType="Tabbed"
|
||||
Height="230"
|
||||
Width="300"
|
||||
Width="320"
|
||||
MinHeight="160"
|
||||
MinWidth="300"
|
||||
MinWidth="320"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
ExtendsContentIntoTitleBar="True">
|
||||
<Grid>
|
||||
@@ -48,7 +48,8 @@
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right" Margin="5">
|
||||
<Button x:Name="okButton" Content="{DynamicResource ok }" HorizontalAlignment="Left" Margin="0 0 5 0" VerticalAlignment="Top" Width="100" Click="OkButton_Click" Cursor="Hand" />
|
||||
<Button x:Name="okButton" Content=".NET Plugin" HorizontalAlignment="Left" Margin="0 0 5 0" VerticalAlignment="Top" Width="100" Click="DotNetButton_Click" Cursor="Hand" />
|
||||
<Button x:Name="webPluginButton" Content="Web Plugin" HorizontalAlignment="Left" Margin="0 0 5 0" VerticalAlignment="Top" Width="100" Click="WebPluginButton_Click" Cursor="Hand" />
|
||||
<Button x:Name="cancelButton" Content="{DynamicResource cancel }" HorizontalAlignment="Left" Margin="0 0 0 0" VerticalAlignment="Top" Width="100" Click="CancelButton_Click" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
@@ -10,6 +10,8 @@ public partial class CreatePluginDialog : Wpf.Ui.Controls.FluentWindow
|
||||
set => textBox.Text = value;
|
||||
}
|
||||
|
||||
public bool IsWebPlugin { get; private set; }
|
||||
|
||||
public CreatePluginDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -17,8 +19,15 @@ public partial class CreatePluginDialog : Wpf.Ui.Controls.FluentWindow
|
||||
Resources.MergedDictionaries.Add(App.LanguageDictionary);
|
||||
}
|
||||
|
||||
private void OkButton_Click(object sender, RoutedEventArgs e)
|
||||
private void DotNetButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
IsWebPlugin = false;
|
||||
DialogResult = true;
|
||||
}
|
||||
|
||||
private void WebPluginButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
IsWebPlugin = true;
|
||||
DialogResult = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -486,6 +486,15 @@ public partial class PluginManager : Page
|
||||
string pluginMetadataPath = Path.Combine(pluginPath, "metadata.json");
|
||||
await File.WriteAllTextAsync(pluginMetadataPath, JsonSerializer.Serialize(pluginMetadata));
|
||||
|
||||
if (inputDialog.IsWebPlugin)
|
||||
{
|
||||
await CreateNewWebPlugin(pluginPath, pluginName);
|
||||
changed = true;
|
||||
await InitializePluginManager();
|
||||
pluginManagerDataContext.IsLoading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
App.Logger.LogInfo($"Creating .NET project at: {pluginProjectPath}", source: "PluginManager");
|
||||
string cmd = $"new classlib -n {pluginSafeName} -o {pluginProjectPath} -f net8.0 --target-framework-override net8.0-windows7";
|
||||
Process process = Process.Start("dotnet", cmd);
|
||||
@@ -608,6 +617,49 @@ public class {pluginSafeName}Plugin : Plugin
|
||||
pluginManagerDataContext.IsLoading = false;
|
||||
}
|
||||
|
||||
private async Task CreateNewWebPlugin(string pluginPath, string pluginName)
|
||||
{
|
||||
App.Logger.LogInfo($"Creating web plugin at: {pluginPath}", source: "PluginManager");
|
||||
|
||||
string mainHtml = $@"<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset=""utf-8"">
|
||||
<style>
|
||||
body {{ margin: 0; padding: 16px; font-family: var(--font-family); }}
|
||||
h1 {{ color: var(--primary-color); }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1 id=""greeting"">{pluginName}</h1>
|
||||
<script>
|
||||
function render() {{
|
||||
const s = window.desktopMagic.getSettings();
|
||||
document.getElementById('greeting').innerHTML = s.message;
|
||||
document.getElementById('greeting').style.color = s.color;
|
||||
document.getElementById('greeting').style.fontWeight = s.bold ? 'bold' : 'normal';
|
||||
}}
|
||||
|
||||
window.desktopMagic.onSettingChanged = (id, value) => render();
|
||||
render();
|
||||
</script>
|
||||
</body>
|
||||
</html>";
|
||||
|
||||
var settings = new List<Dictionary<string, object?>>
|
||||
{
|
||||
new() { ["id"] = "message", ["name"] = "Greeting Message", ["type"] = "textbox", ["default"] = "Hello, World!" },
|
||||
new() { ["id"] = "bold", ["name"] = "Bold Text", ["type"] = "checkbox", ["default"] = true },
|
||||
new() { ["id"] = "color", ["name"] = "Text Color", ["type"] = "colorpicker", ["default"] = "#FF5722" }
|
||||
};
|
||||
|
||||
string settingsJson = JsonSerializer.Serialize(settings, new JsonSerializerOptions { WriteIndented = true });
|
||||
await File.WriteAllTextAsync(Path.Combine(pluginPath, "settings.json"), settingsJson);
|
||||
await File.WriteAllTextAsync(Path.Combine(pluginPath, "main.html"), mainHtml);
|
||||
|
||||
App.Logger.LogInfo($"Successfully created web plugin: {pluginName}", source: "PluginManager");
|
||||
}
|
||||
|
||||
private async void LogInButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (pluginManagerDataContext.IsAuthenticated)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using DesktopMagic.Api.Settings;
|
||||
using DesktopMagic.Helpers;
|
||||
using DesktopMagic.Plugins;
|
||||
using DesktopMagic.Settings;
|
||||
@@ -5,7 +6,10 @@ using DesktopMagic.Settings;
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
@@ -193,6 +197,8 @@ public partial class WebPluginWindow : Window, IPluginWindow
|
||||
|
||||
try
|
||||
{
|
||||
LoadWebPluginSettings();
|
||||
|
||||
string userDataFolder = Path.Combine(Path.GetTempPath(), "DesktopMagic", "WebView2", PluginMetadata.Id.ToString());
|
||||
CoreWebView2Environment environment = await CoreWebView2Environment.CreateAsync(null, userDataFolder);
|
||||
await webView.EnsureCoreWebView2Async(environment);
|
||||
@@ -202,6 +208,8 @@ public partial class WebPluginWindow : Window, IPluginWindow
|
||||
webView.CoreWebView2.Settings.IsStatusBarEnabled = false;
|
||||
webView.CoreWebView2.Settings.AreDefaultScriptDialogsEnabled = true;
|
||||
|
||||
_ = await webView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(GetSettingsBridgeScript());
|
||||
|
||||
string htmlUri = new Uri(htmlPath).AbsoluteUri;
|
||||
webView.Source = new Uri(htmlUri);
|
||||
|
||||
@@ -251,6 +259,268 @@ public partial class WebPluginWindow : Window, IPluginWindow
|
||||
|
||||
private void WebView_CoreWebView2InitializationCompleted(object sender, CoreWebView2InitializationCompletedEventArgs e)
|
||||
{
|
||||
webView.CoreWebView2.DOMContentLoaded += (_, _) => ThemeChanged();
|
||||
webView.CoreWebView2.DOMContentLoaded += (_, _) =>
|
||||
{
|
||||
ThemeChanged();
|
||||
};
|
||||
}
|
||||
|
||||
private void LoadWebPluginSettings()
|
||||
{
|
||||
string settingsPath = Path.Combine(PluginFolderPath, "settings.json");
|
||||
if (!File.Exists(settingsPath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
string json = File.ReadAllText(settingsPath);
|
||||
using JsonDocument doc = JsonDocument.Parse(json);
|
||||
JsonElement root = doc.RootElement;
|
||||
|
||||
if (root.ValueKind != JsonValueKind.Array)
|
||||
{
|
||||
App.Logger.LogWarn($"\"{PluginMetadata.Name}\" - settings.json must be a JSON array", source: "WebPlugin");
|
||||
return;
|
||||
}
|
||||
|
||||
List<SettingElement> settingElements = [];
|
||||
int orderIndex = 0;
|
||||
|
||||
foreach (JsonElement element in root.EnumerateArray())
|
||||
{
|
||||
string id = element.GetProperty("id").GetString() ?? $"setting-{orderIndex}";
|
||||
string name = element.TryGetProperty("name", out JsonElement nameEl) ? nameEl.GetString() ?? id : id;
|
||||
string type = element.TryGetProperty("type", out JsonElement typeEl) ? typeEl.GetString() ?? "textbox" : "textbox";
|
||||
|
||||
Setting? setting = CreateSetting(type, element);
|
||||
if (setting is null)
|
||||
{
|
||||
App.Logger.LogWarn($"\"{PluginMetadata.Name}\" - Unknown setting type \"{type}\" for \"{id}\"", source: "WebPlugin");
|
||||
continue;
|
||||
}
|
||||
|
||||
SettingElement settingElement = new(setting, id, name, orderIndex);
|
||||
|
||||
if (settings.Settings.Exists(e => e.Id == id))
|
||||
{
|
||||
SettingElement saved = settings.Settings.First(e => e.Id == id);
|
||||
settingElement.JsonValue = saved.JsonValue;
|
||||
}
|
||||
|
||||
string capturedId = id;
|
||||
if (setting is Button button)
|
||||
{
|
||||
button.OnClick += () =>
|
||||
{
|
||||
_ = webView.Dispatcher.InvokeAsync(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
_ = await webView.ExecuteScriptAsync($"window.desktopMagic?.onClick?.({JsonSerializer.Serialize(capturedId)})");
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
setting.OnValueChanged += () =>
|
||||
{
|
||||
_ = webView.Dispatcher.InvokeAsync(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await NotifySettingChange(capturedId, setting);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
settingElements.Add(settingElement);
|
||||
orderIndex++;
|
||||
}
|
||||
|
||||
settings.Settings = [.. settingElements.OrderBy(x => x.OrderIndex)];
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
App.Logger.LogWarn($"\"{PluginMetadata.Name}\" - Failed to load settings from settings.json: {ex.Message}", source: "WebPlugin");
|
||||
}
|
||||
}
|
||||
|
||||
private string GetSettingsBridgeScript()
|
||||
{
|
||||
string settingsJson = SerializeSettingsToJson();
|
||||
|
||||
return $@"
|
||||
(function() {{
|
||||
window.desktopMagic = {{}};
|
||||
window.desktopMagic._settings = {settingsJson};
|
||||
|
||||
window.desktopMagic.getSettings = function() {{
|
||||
return JSON.parse(JSON.stringify(window.desktopMagic._settings));
|
||||
}};
|
||||
|
||||
window.desktopMagic.getSetting = function(id) {{
|
||||
return window.desktopMagic._settings ? window.desktopMagic._settings[id] : undefined;
|
||||
}};
|
||||
|
||||
window.desktopMagic.onSettingChanged = null;
|
||||
window.desktopMagic.onButtonClick = null;
|
||||
|
||||
window.desktopMagic.dispatchSettingChanged = function(id, value) {{
|
||||
if (window.desktopMagic._settings) {{
|
||||
window.desktopMagic._settings[id] = value;
|
||||
}}
|
||||
if (typeof window.desktopMagic.onSettingChanged === 'function') {{
|
||||
window.desktopMagic.onSettingChanged(id, value);
|
||||
}}
|
||||
}};
|
||||
|
||||
window.desktopMagic.onClick = function(id) {{
|
||||
if (typeof window.desktopMagic.onButtonClick === 'function') {{
|
||||
window.desktopMagic.onButtonClick(id);
|
||||
}}
|
||||
}};
|
||||
}})();
|
||||
";
|
||||
}
|
||||
|
||||
private async System.Threading.Tasks.Task NotifySettingChange(string id, Setting setting)
|
||||
{
|
||||
object? value = GetSettingValue(setting);
|
||||
string serializedValue = JsonSerializer.Serialize(value);
|
||||
string serializedId = JsonSerializer.Serialize(id);
|
||||
_ = await webView.ExecuteScriptAsync($"window.desktopMagic?.dispatchSettingChanged?.({serializedId}, {serializedValue})");
|
||||
}
|
||||
|
||||
private string SerializeSettingsToJson()
|
||||
{
|
||||
Dictionary<string, object?> dict = [];
|
||||
foreach (SettingElement element in settings.Settings)
|
||||
{
|
||||
dict[element.Id] = GetSettingValue(element.Input);
|
||||
}
|
||||
return JsonSerializer.Serialize(dict);
|
||||
}
|
||||
|
||||
private static object? GetSettingValue(Setting setting)
|
||||
{
|
||||
return setting switch
|
||||
{
|
||||
TextBox tb => tb.Value,
|
||||
CheckBox cb => cb.Value,
|
||||
Slider sl => sl.Value,
|
||||
IntegerUpDown iud => iud.Value,
|
||||
ComboBox cb => cb.Value,
|
||||
ColorPicker cp => $"#{cp.Value.A:X2}{cp.Value.R:X2}{cp.Value.G:X2}{cp.Value.B:X2}",
|
||||
Button btn => btn.Value,
|
||||
Label lbl => lbl.Value,
|
||||
FileSelector fs => fs.Value,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
private static Setting? CreateSetting(string type, JsonElement element)
|
||||
{
|
||||
return type.ToLowerInvariant() switch
|
||||
{
|
||||
"textbox" => new TextBox(
|
||||
element.TryGetProperty("default", out JsonElement tbDefault) ? tbDefault.GetString() ?? "" : ""),
|
||||
|
||||
"checkbox" => new CheckBox(
|
||||
element.TryGetProperty("default", out JsonElement cbDefault) && cbDefault.ValueKind == JsonValueKind.True),
|
||||
|
||||
"slider" => new Slider(
|
||||
element.TryGetProperty("min", out JsonElement slMin) ? slMin.GetDouble() : 0,
|
||||
element.TryGetProperty("max", out JsonElement slMax) ? slMax.GetDouble() : 100,
|
||||
element.TryGetProperty("default", out JsonElement slDefault) ? slDefault.GetDouble() : 0),
|
||||
|
||||
"integer" => new IntegerUpDown(
|
||||
element.TryGetProperty("min", out JsonElement intMin) ? intMin.GetInt32() : 0,
|
||||
element.TryGetProperty("max", out JsonElement intMax) ? intMax.GetInt32() : 100,
|
||||
element.TryGetProperty("default", out JsonElement intDefault) ? intDefault.GetInt32() : 0),
|
||||
|
||||
"combobox" => CreateComboBox(element),
|
||||
|
||||
"colorpicker" => new ColorPicker(
|
||||
element.TryGetProperty("default", out JsonElement cpDefault)
|
||||
? ParseHexColor(cpDefault.GetString() ?? "#FFFFFFFF")
|
||||
: System.Drawing.Color.White),
|
||||
|
||||
"button" => new Button(
|
||||
element.TryGetProperty("default", out JsonElement btnDefault) ? btnDefault.GetString() ?? "" : ""),
|
||||
|
||||
"label" => new Label(
|
||||
element.TryGetProperty("default", out JsonElement lblDefault) ? lblDefault.GetString() ?? "" : "",
|
||||
element.TryGetProperty("bold", out JsonElement boldEl) && boldEl.ValueKind == JsonValueKind.True),
|
||||
|
||||
"file" => new FileSelector(
|
||||
element.TryGetProperty("default", out JsonElement fsDefault) ? fsDefault.GetString() ?? "" : "",
|
||||
element.TryGetProperty("filter", out JsonElement filterEl) ? filterEl.GetString() ?? "All Files|*.*" : "All Files|*.*",
|
||||
element.TryGetProperty("title", out JsonElement titleEl) ? titleEl.GetString() ?? "Select File" : "Select File",
|
||||
element.TryGetProperty("selectFolder", out JsonElement sfEl) && sfEl.ValueKind == JsonValueKind.True),
|
||||
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
private static ComboBox CreateComboBox(JsonElement element)
|
||||
{
|
||||
List<string> items = [];
|
||||
if (element.TryGetProperty("items", out JsonElement itemsEl) && itemsEl.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
foreach (JsonElement item in itemsEl.EnumerateArray())
|
||||
{
|
||||
items.Add(item.GetString() ?? "");
|
||||
}
|
||||
}
|
||||
|
||||
ComboBox comboBox = new([.. items]);
|
||||
|
||||
if (element.TryGetProperty("default", out JsonElement cbDefault))
|
||||
{
|
||||
string defaultVal = cbDefault.GetString() ?? "";
|
||||
if (!string.IsNullOrEmpty(defaultVal) && items.Contains(defaultVal))
|
||||
{
|
||||
comboBox.Value = defaultVal;
|
||||
}
|
||||
}
|
||||
|
||||
return comboBox;
|
||||
}
|
||||
|
||||
private static System.Drawing.Color ParseHexColor(string hex)
|
||||
{
|
||||
if (string.IsNullOrEmpty(hex))
|
||||
{
|
||||
return System.Drawing.Color.White;
|
||||
}
|
||||
|
||||
if (hex.StartsWith("#"))
|
||||
{
|
||||
hex = hex[1..];
|
||||
}
|
||||
|
||||
if (hex.Length == 6)
|
||||
{
|
||||
hex = "FF" + hex;
|
||||
}
|
||||
|
||||
if (hex.Length == 8 &&
|
||||
byte.TryParse(hex[..2], System.Globalization.NumberStyles.HexNumber, null, out byte a) &&
|
||||
byte.TryParse(hex[2..4], System.Globalization.NumberStyles.HexNumber, null, out byte r) &&
|
||||
byte.TryParse(hex[4..6], System.Globalization.NumberStyles.HexNumber, null, out byte g) &&
|
||||
byte.TryParse(hex[6..8], System.Globalization.NumberStyles.HexNumber, null, out byte b))
|
||||
{
|
||||
return System.Drawing.Color.FromArgb(a, r, g, b);
|
||||
}
|
||||
|
||||
return System.Drawing.Color.White;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user