mirror of
https://github.com/Stone-Red-Code/DesktopMagic.git
synced 2026-09-04 00:46:12 +02:00
Add hot reload support for web plugins with file watcher and debounce
This commit is contained in:
@@ -10,6 +10,7 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Interop;
|
using System.Windows.Interop;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
@@ -24,6 +25,9 @@ public partial class WebPluginWindow : Window, IPluginWindow
|
|||||||
|
|
||||||
private readonly PluginSettings settings;
|
private readonly PluginSettings settings;
|
||||||
private bool isInitialized = false;
|
private bool isInitialized = false;
|
||||||
|
private FileSystemWatcher? fileWatcher;
|
||||||
|
private System.Timers.Timer? reloadDebounceTimer;
|
||||||
|
private bool isReloading = false;
|
||||||
|
|
||||||
public bool IsRunning { get; private set; } = true;
|
public bool IsRunning { get; private set; } = true;
|
||||||
public PluginMetadata PluginMetadata { get; private set; }
|
public PluginMetadata PluginMetadata { get; private set; }
|
||||||
@@ -75,6 +79,11 @@ public partial class WebPluginWindow : Window, IPluginWindow
|
|||||||
Height = settings.Size.Y;
|
Height = settings.Size.Y;
|
||||||
|
|
||||||
PluginFolderPath = pluginFolderPath;
|
PluginFolderPath = pluginFolderPath;
|
||||||
|
|
||||||
|
if (pluginMetadata.SupportsUnloading && !string.IsNullOrEmpty(pluginFolderPath))
|
||||||
|
{
|
||||||
|
InitializeHotReload();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Exit()
|
public void Exit()
|
||||||
@@ -232,6 +241,15 @@ public partial class WebPluginWindow : Window, IPluginWindow
|
|||||||
App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - Stopping web plugin", source: "WebPlugin");
|
App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - Stopping web plugin", source: "WebPlugin");
|
||||||
IsRunning = false;
|
IsRunning = false;
|
||||||
|
|
||||||
|
if (fileWatcher != null)
|
||||||
|
{
|
||||||
|
fileWatcher.EnableRaisingEvents = false;
|
||||||
|
fileWatcher.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadDebounceTimer?.Stop();
|
||||||
|
reloadDebounceTimer?.Dispose();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (isInitialized && webView.CoreWebView2 != null)
|
if (isInitialized && webView.CoreWebView2 != null)
|
||||||
@@ -257,10 +275,21 @@ public partial class WebPluginWindow : Window, IPluginWindow
|
|||||||
tileBar.CaptionHeight = ActualHeight - 10;
|
tileBar.CaptionHeight = ActualHeight - 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool firstLoad = true;
|
||||||
|
|
||||||
private void WebView_CoreWebView2InitializationCompleted(object sender, CoreWebView2InitializationCompletedEventArgs e)
|
private void WebView_CoreWebView2InitializationCompleted(object sender, CoreWebView2InitializationCompletedEventArgs e)
|
||||||
{
|
{
|
||||||
webView.CoreWebView2.DOMContentLoaded += (_, _) =>
|
webView.CoreWebView2.DOMContentLoaded += (_, _) =>
|
||||||
{
|
{
|
||||||
|
if (firstLoad)
|
||||||
|
{
|
||||||
|
firstLoad = false;
|
||||||
|
if (fileWatcher != null)
|
||||||
|
{
|
||||||
|
fileWatcher.EnableRaisingEvents = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ThemeChanged();
|
ThemeChanged();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -320,8 +349,9 @@ public partial class WebPluginWindow : Window, IPluginWindow
|
|||||||
{
|
{
|
||||||
_ = await webView.ExecuteScriptAsync($"window.desktopMagic?.onClick?.({JsonSerializer.Serialize(capturedId)})");
|
_ = await webView.ExecuteScriptAsync($"window.desktopMagic?.onClick?.({JsonSerializer.Serialize(capturedId)})");
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
App.Logger.LogError($"\"{PluginMetadata.Name}\" - Failed to notify button click for \"{capturedId}\": {ex}", source: "WebPlugin");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -335,8 +365,9 @@ public partial class WebPluginWindow : Window, IPluginWindow
|
|||||||
{
|
{
|
||||||
await NotifySettingChange(capturedId, setting);
|
await NotifySettingChange(capturedId, setting);
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
App.Logger.LogError($"\"{PluginMetadata.Name}\" - Failed to notify setting change for \"{capturedId}\": {ex}", source: "WebPlugin");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -391,6 +422,111 @@ public partial class WebPluginWindow : Window, IPluginWindow
|
|||||||
";
|
";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void InitializeHotReload()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
fileWatcher = new FileSystemWatcher(PluginFolderPath)
|
||||||
|
{
|
||||||
|
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size,
|
||||||
|
EnableRaisingEvents = false
|
||||||
|
};
|
||||||
|
|
||||||
|
fileWatcher.Changed += OnPluginFileChanged;
|
||||||
|
|
||||||
|
reloadDebounceTimer = new System.Timers.Timer(500)
|
||||||
|
{
|
||||||
|
AutoReset = false
|
||||||
|
};
|
||||||
|
reloadDebounceTimer.Elapsed += async (s, e) =>
|
||||||
|
{
|
||||||
|
await ReloadWebPlugin();
|
||||||
|
};
|
||||||
|
|
||||||
|
App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - Hot reload enabled", source: "WebPlugin");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
App.Logger.LogWarn($"\"{PluginMetadata.Name}\" - Failed to initialize hot reload: {ex.Message}", source: "WebPlugin");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPluginFileChanged(object sender, FileSystemEventArgs e)
|
||||||
|
{
|
||||||
|
if (isReloading)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string? fileName = e.Name;
|
||||||
|
if (string.IsNullOrEmpty(fileName))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string ext = Path.GetExtension(fileName).ToLowerInvariant();
|
||||||
|
if (ext is not ".html" and not ".json" and not ".js" and not ".css")
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadDebounceTimer?.Stop();
|
||||||
|
reloadDebounceTimer?.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ReloadWebPlugin()
|
||||||
|
{
|
||||||
|
if (isReloading || !IsRunning)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isReloading = true;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - Reloading web plugin", source: "WebPlugin");
|
||||||
|
|
||||||
|
if (fileWatcher != null)
|
||||||
|
{
|
||||||
|
fileWatcher.EnableRaisingEvents = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadWebPluginSettings();
|
||||||
|
|
||||||
|
await Dispatcher.Invoke(async () =>
|
||||||
|
{
|
||||||
|
if (isInitialized && webView.CoreWebView2 != null)
|
||||||
|
{
|
||||||
|
_ = await webView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(GetSettingsBridgeScript());
|
||||||
|
|
||||||
|
busyMask.IsBusy = true;
|
||||||
|
|
||||||
|
string htmlPath = Path.Combine(PluginFolderPath, "main.html");
|
||||||
|
string htmlUri = new Uri(htmlPath).AbsoluteUri;
|
||||||
|
webView.CoreWebView2.Navigate(htmlUri);
|
||||||
|
|
||||||
|
busyMask.IsBusy = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - Web plugin reloaded", source: "WebPlugin");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
App.Logger.LogError($"\"{PluginMetadata.Name}\" - Failed to reload web plugin: {ex}", source: "WebPlugin");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
isReloading = false;
|
||||||
|
|
||||||
|
if (fileWatcher != null && IsRunning)
|
||||||
|
{
|
||||||
|
fileWatcher.EnableRaisingEvents = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async System.Threading.Tasks.Task NotifySettingChange(string id, Setting setting)
|
private async System.Threading.Tasks.Task NotifySettingChange(string id, Setting setting)
|
||||||
{
|
{
|
||||||
object? value = GetSettingValue(setting);
|
object? value = GetSettingValue(setting);
|
||||||
@@ -430,11 +566,9 @@ public partial class WebPluginWindow : Window, IPluginWindow
|
|||||||
{
|
{
|
||||||
return type.ToLowerInvariant() switch
|
return type.ToLowerInvariant() switch
|
||||||
{
|
{
|
||||||
"textbox" => new TextBox(
|
"textbox" => new TextBox(element.TryGetProperty("default", out JsonElement tbDefault) ? tbDefault.GetString() ?? "" : ""),
|
||||||
element.TryGetProperty("default", out JsonElement tbDefault) ? tbDefault.GetString() ?? "" : ""),
|
|
||||||
|
|
||||||
"checkbox" => new CheckBox(
|
"checkbox" => new CheckBox(element.TryGetProperty("default", out JsonElement cbDefault) && cbDefault.ValueKind == JsonValueKind.True),
|
||||||
element.TryGetProperty("default", out JsonElement cbDefault) && cbDefault.ValueKind == JsonValueKind.True),
|
|
||||||
|
|
||||||
"slider" => new Slider(
|
"slider" => new Slider(
|
||||||
element.TryGetProperty("min", out JsonElement slMin) ? slMin.GetDouble() : 0,
|
element.TryGetProperty("min", out JsonElement slMin) ? slMin.GetDouble() : 0,
|
||||||
@@ -453,8 +587,7 @@ public partial class WebPluginWindow : Window, IPluginWindow
|
|||||||
? ParseHexColor(cpDefault.GetString() ?? "#FFFFFFFF")
|
? ParseHexColor(cpDefault.GetString() ?? "#FFFFFFFF")
|
||||||
: System.Drawing.Color.White),
|
: System.Drawing.Color.White),
|
||||||
|
|
||||||
"button" => new Button(
|
"button" => new Button(element.TryGetProperty("default", out JsonElement btnDefault) ? btnDefault.GetString() ?? "" : ""),
|
||||||
element.TryGetProperty("default", out JsonElement btnDefault) ? btnDefault.GetString() ?? "" : ""),
|
|
||||||
|
|
||||||
"label" => new Label(
|
"label" => new Label(
|
||||||
element.TryGetProperty("default", out JsonElement lblDefault) ? lblDefault.GetString() ?? "" : "",
|
element.TryGetProperty("default", out JsonElement lblDefault) ? lblDefault.GetString() ?? "" : "",
|
||||||
@@ -502,7 +635,7 @@ public partial class WebPluginWindow : Window, IPluginWindow
|
|||||||
return System.Drawing.Color.White;
|
return System.Drawing.Color.White;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hex.StartsWith("#"))
|
if (hex.StartsWith('#'))
|
||||||
{
|
{
|
||||||
hex = hex[1..];
|
hex = hex[1..];
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user