Skip to content

Commit 3b8b18e

Browse files
committed
Switch to a new internal function/event registration system
1 parent b0d0ead commit 3b8b18e

File tree

7 files changed

+439
-101
lines changed

7 files changed

+439
-101
lines changed

examples/WpfControllerApp/MainWindow.xaml.cs

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,32 +38,61 @@ public WinDispatcher(Dispatcher dispatcher)
3838

3939
public void InvokeWithVoid(Action action) => dispatcher.Invoke(action);
4040
}
41-
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
41+
private async void MainWindow_Loaded(object sender, RoutedEventArgs e)
4242
{
4343
WebUI.Window.UseSpecificDispatcher = new WinDispatcher(Dispatcher);
4444

4545
window = new(new WebUI.WindowProperties { Width = 640, Height = 480, X = 800, Y = 50 });
4646
var evts = window.RegisterDefaultEventHandler();
47-
evts.OnDisconnect += (_) =>
47+
48+
//evts.OnDisconnect += (_) =>
49+
//{
50+
// window = null;
51+
// Dispatcher.InvokeAsync(() => Close());
52+
//};
53+
54+
window.InitOurBridge();
55+
//window.Show("<html><head><script src='webui.js'></script></head><body>Hi Bob</body></html>");
56+
//await Task.Delay(2000);
57+
//btnDevTools_Click(null, null);
58+
// await Task.Delay(2000);
59+
60+
evts.OnConnect += (window) =>
4861
{
49-
window = null;
50-
Dispatcher.InvokeAsync(() => Close());
62+
//this.window = window;
63+
NewConnect();
5164
};
65+
66+
67+
5268
window.Show("index.html");
5369

54-
window.RegisterOnClick(JSClickedButton, "TestButton");
55-
window.RegisterBoundFunction(() => MyTest_function);
70+
//window.RegisterBoundFunction(() => MyTest_function);
71+
}
72+
private async void NewConnect()
73+
{
74+
window.SetDebug();
75+
window.AddEventListener(JSClickedButton, CommonEventTypes.click, "TestButton");
76+
window.RegisterBoundFunction("MyTest_function", RawMyTestCalled);
77+
5678
}
79+
80+
private Task<string> RawMyTestCalled(string serialized)
81+
{
82+
var args = Newtonsoft.Json.JsonConvert.DeserializeObject<string[]>(serialized);
83+
return MyTest_function(args[0], args[1], double.Parse(args[2]));
84+
}
85+
5786
private async Task<string> MyTest_function(String arg1, String arg2, double arg3)
5887
{
5988
var str = $"I am called: {arg1}({arg1.GetType()}) and {arg2}({arg2.GetType()}) {arg3}({arg3.GetType()})";
6089
LogItem(str);
6190
await Task.Delay(500);
6291
return "Nice Call " + arg1;
6392
}
64-
private void JSClickedButton(String elem)
93+
private void JSClickedButton(DomEvent evt)
6594
{
66-
LogItem($"Got a click from JS for: {elem}");
95+
LogItem($"Got a click from JS for: {evt.originalTargetId}");
6796
}
6897

6998
private void LogItem(object val, [CallerArgumentExpression(nameof(val))] string expression = "unknown")

examples/WpfControllerApp/index.html

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
44
<head>
55
<meta charset="utf-8" />
6-
<script src="webui.js"></script>
6+
<script src="webui.js"></script>
7+
<script src="webui_net.js"></script>
78
<title>JS Interop</title>
89
<script>
910

@@ -25,7 +26,7 @@
2526
const getComplexObj = () => complexObj;
2627

2728
async function btn2Clicked() {
28-
let res = await webui.call("MyTest_function", "an", "arg", 12345.78);
29+
let res = await MyTest_function("1st JS Arg Passed to C#", "arg", 12345.78);
2930
alert("You returned: " + res);
3031
}
3132

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using System;
2+
using Newtonsoft.Json;
3+
4+
namespace WebUI
5+
{
6+
public class MilisecondEpochConverter : RandomNumberTimeConverter<long, DateTime>
7+
{
8+
private static readonly DateTime _epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
9+
10+
public MilisecondEpochConverter() : base(long.Parse) { }
11+
12+
public static DateTime ParseLong(long val) => _epoch.AddMilliseconds(val);
13+
14+
protected override DateTime ParseValue(long val) => ParseLong(val);
15+
16+
protected override string WriteValue(DateTime when)
17+
{
18+
return ((long)(when - _epoch).TotalMilliseconds).ToString();
19+
}
20+
public override bool CanConvert(Type objectType)
21+
{
22+
if (objectType == typeof(DateTime) || objectType == typeof(DateTime?))
23+
{
24+
return true;
25+
}
26+
27+
if (objectType == typeof(DateTimeOffset) || objectType == typeof(DateTimeOffset?))
28+
{
29+
return true;
30+
}
31+
32+
return false;
33+
}
34+
}
35+
public abstract class RandomNumberTimeConverter<T, SOURCE_TYPE> : JsonConverter
36+
{
37+
protected RandomNumberTimeConverter(Func<String, T> ParseNotNullStringHandler)
38+
{
39+
this.ParseNotNullStringHandler = ParseNotNullStringHandler;
40+
}
41+
Func<String, T> ParseNotNullStringHandler;
42+
43+
protected abstract SOURCE_TYPE ParseValue(T val);
44+
protected abstract String WriteValue(SOURCE_TYPE when);
45+
46+
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
47+
{
48+
writer.WriteRawValue(WriteValue((SOURCE_TYPE)value));
49+
50+
51+
}
52+
53+
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
54+
{
55+
if (reader.Value == null) { return null; }
56+
T val;
57+
if (reader.TokenType == JsonToken.String)
58+
val = ParseNotNullStringHandler((string)reader.Value);
59+
else
60+
val = (T)Convert.ChangeType(reader.Value, typeof(T));
61+
return ParseValue(val);
62+
}
63+
}
64+
}

src/WebUI.NET/WebUI.NET.csproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,10 @@
6464
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
6565
</ItemGroup>
6666

67+
<ItemGroup>
68+
<None Update="webui_net.js">
69+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
70+
</None>
71+
</ItemGroup>
72+
6773
</Project>

src/WebUI.NET/WebUINetUI.cs

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
using System;
2+
using System.Collections.Concurrent;
3+
using System.Collections.Generic;
4+
using System.Diagnostics;
5+
using System.IO;
6+
using System.Linq;
7+
using System.Runtime.CompilerServices;
8+
using System.Text;
9+
using System.Threading;
10+
using System.Threading.Tasks;
11+
using Newtonsoft.Json;
12+
13+
namespace WebUI
14+
{
15+
16+
public class DomEvent
17+
{
18+
19+
public string currentTargetId { get; set; }
20+
public string originalTargetId { get; set; }
21+
[JsonConverter(typeof(MilisecondEpochConverter))]
22+
public DateTime timestamp { get; set; }
23+
public string type { get; set; }
24+
public Dictionary<string, string> additionalProps; //map of additional property values requested
25+
26+
}
27+
public enum CommonEventTypes { load, unload, error, resize, scroll, focus, blur, click, dblclick, mousedown, mouseup, mouseover, mouseout, mousemove, input, keydown, keypress, keyup, submit, change, DOMNodeInserted, DOMNodeRemoved, DOMSubtreeModified, DOMNodeInsertedIntoDocument, DOMNodeRemovedFromDocument, DOMContentLoaded, hashchange, beforeunload }
28+
public static class WebUINetUI
29+
{
30+
31+
public class EventOpts
32+
{
33+
public bool capture { get; set; }
34+
public bool once { get; set; }
35+
public string abortKey { get; set; }
36+
37+
}
38+
private const int startId = 5432;
39+
private static volatile int curFuncId = startId;
40+
// CommonEventTypes
41+
public static void AddEventListener(this Window window, Action<DomEvent> OnFired, CommonEventTypes eventType, String domId, EventOpts additionalOpts = null, params KeyValuePair<string, string>[] additionalEventCaptureProps) => window.AddEventListener(OnFired, eventType.ToString(), domId, additionalOpts, additionalEventCaptureProps);
42+
43+
public static void AddEventListener(this Window window, Action<DomEvent> OnFired, String eventType, String domId, EventOpts additionalOpts = null, params KeyValuePair<string, string>[] additionalEventCaptureProps)
44+
{
45+
46+
//addCSEventListener(type, elementID, csFuncID, additionalProps, options, abortKey)
47+
var addlDict = additionalEventCaptureProps?.Length > 0 ?
48+
additionalEventCaptureProps.ToDictionary(x => x.Key, x => x.Value) : null;
49+
var abortKey = additionalOpts?.abortKey;
50+
if (abortKey != null)
51+
additionalOpts.abortKey = null;
52+
var curId = window._RegisterBoundFunction(OnCalled: (str) => OnFired(Newtonsoft.Json.JsonConvert.DeserializeObject<DomEvent[]>(str)[0]), registerFunc: false);
53+
window.InvokeJavascriptMethod($"{ourJSClass}.addCSEventListener", eventType, domId, curId, addlDict, additionalOpts, abortKey);
54+
}
55+
private static async void JavascriptFuncCallback(this Window window, int csFuncId, int jsCallId, string jsonOfArgs)
56+
{
57+
if (!csFuncIdToFired.TryGetValue(csFuncId, out var info))
58+
throw new Exception($"The JS func callback handler was passed an invalid function id {csFuncId}"); ;
59+
if (info.normFunc != null)
60+
{
61+
Window.UseSpecificDispatcher.InvokeWithVoid(() => info.normFunc(jsonOfArgs));
62+
return;
63+
}
64+
var res = await Window.UseSpecificDispatcher.InvokeWithReturnType(() => info.taskFunc(jsonOfArgs));
65+
66+
await window.ScriptEvaluateMethod<string>($"{ourJSClass}.setCSFunctionResult", jsCallId, res);//we don't use invoke so we can get the error
67+
//window.InvokeJavascriptMethod("setCSFunctionResult", jsCallId, res);
68+
69+
}
70+
private const string ourJSClass = $"window.WebUINet";
71+
public static void SetDebug(this Window window, bool debugging = true)
72+
{
73+
var jsDebug = debugging ? "true" : "false";
74+
window.InvokeJavaScript($@"{ourJSClass}.DEBUG_MODE={jsDebug};
75+
window.WebuiBridge.setLogging({jsDebug});
76+
");
77+
}
78+
public static void RegisterBoundFunction(this Window window, String RegisteredName, Func<string, Task<string>> OnCalled) => window._RegisterBoundFunction(RegisteredName, OnCalledTask: OnCalled);
79+
public static void RegisterBoundFunction(this Window window, String RegisteredName, Action<string> OnCalled) => window._RegisterBoundFunction(RegisteredName, OnCalled);
80+
81+
private static int _RegisterBoundFunction(this Window window, String RegisteredName = null, Action<string> OnCalled = null, Func<string, Task<string>> OnCalledTask = null, bool registerFunc = true)
82+
{
83+
var curId = Interlocked.Increment(ref curFuncId);
84+
csFuncIdToFired[curId] = new RegisteredFunc { taskFunc = OnCalledTask, normFunc = OnCalled };
85+
if (registerFunc)
86+
{
87+
//we don't use invoke so we can get the error
88+
window.ScriptEvaluateMethod<string>($"{ourJSClass}.addCSFunction", curId, RegisteredName, OnCalledTask != null); //not awaiting but exception will at least show in logs just no ST
89+
//window.InvokeJavascriptMethod("WebUINet.addCSFunction", curId, RegisteredName, OnCalledTask != null);
90+
}
91+
if (curId == startId + 1)
92+
{
93+
window.RegisterEventHandler(RawFuncCallback, "webuiNet_Callback");
94+
}
95+
return curId;
96+
}
97+
public static void InitOurBridge(this Window window)
98+
{
99+
OurJSBytes = Encoding.UTF8.GetBytes(File.ReadAllText("webui_net.js"));
100+
window.SetFileHandler((path) => path == "/webui_net.js" ? OurJSBytes : null);
101+
}
102+
private static byte[] OurJSBytes;
103+
private static object RawFuncCallback(WebUI.Events.Event evt, string element, ulong handlerId)
104+
{
105+
evt.Window.JavascriptFuncCallback((int)evt.GetNumber(0), (int)evt.GetNumber(1), evt.GetString(2));
106+
return null;
107+
}
108+
109+
private class RegisteredFunc
110+
{
111+
public Func<string, Task<string>> taskFunc;
112+
public Action<string> normFunc;
113+
}
114+
private static ConcurrentDictionary<int, RegisteredFunc> csFuncIdToFired = new();
115+
}
116+
}

0 commit comments

Comments
 (0)