// Ooui v1.0.0 var debug = false; const nodes = {}; const hasText = {}; let socket = null; const mouseEvents = { click: true, dblclick: true, mousedown: true, mouseenter: true, mouseleave: true, mousemove: true, mouseout: true, mouseover: true, mouseup: true, wheel: true, }; // Try to close the socket gracefully window.onbeforeunload = function() { if (socket != null) { socket.close (1001, "Unloading page"); socket = null; console.log ("Web socket closed"); } return null; } function getSize () { return { height: window.innerHeight, width: window.innerWidth }; } function setCookie (name, value, days) { var expires = ""; if (days) { var date = new Date (); date.setTime(date.getTime () + (days*24*60*60*1000)); expires = "; expires=" + date.toUTCString(); } document.cookie = name + "=" + (value || "") + expires + "; path=/"; } function saveSize (s) { setCookie ("oouiWindowWidth", s.width, 7); setCookie ("oouiWindowHeight", s.height, 7); } // Main entrypoint function ooui (rootElementPath) { var initialSize = getSize (); saveSize (initialSize); var wsArgs = (rootElementPath.indexOf("?") >= 0 ? "&" : "?") + "w=" + initialSize.width + "&h=" + initialSize.height; var proto = "ws"; if (location.protocol == "https:") { proto = "wss"; } socket = new WebSocket (proto + "://" + document.location.host + rootElementPath + wsArgs, "ooui"); socket.addEventListener ("open", function (event) { console.log ("Web socket opened"); }); socket.addEventListener ("error", function (event) { console.error ("Web socket error", event); }); socket.addEventListener ("close", function (event) { console.error ("Web socket close", event); }); socket.addEventListener("message", function (event) { const messages = JSON.parse (event.data); if (debug) console.log("Messages", messages); if (Array.isArray (messages)) { messages.forEach (function (m) { // console.log('Raw value from server', m.v); m.v = fixupValue (m.v); processMessage (m); }); } }); console.log("Web socket created"); // Throttled window resize event (function() { window.addEventListener("resize", resizeThrottler, false); var resizeTimeout; function resizeThrottler() { if (!resizeTimeout) { resizeTimeout = setTimeout(function() { resizeTimeout = null; resizeHandler(); }, 100); } } function resizeHandler() { const em = { m: "event", id: "window", k: "resize", v: getSize (), }; saveSize (em.v); const ems = JSON.stringify (em); if (socket != null) socket.send (ems); if (debug) console.log ("Event", em); } }()); } function oouiWasm (mainAsmName, mainNamspace, mainClassName, mainMethodNmae, assemblies) { Module.assemblies = assemblies; var initialSize = getSize (); function send (json) { if (debug) console.log ("Send", json); } function resizeHandler() { const em = { m: "event", id: "window", k: "resize", v: getSize (), }; const ems = JSON.stringify (em); send (ems); if (debug) console.log ("Event", em); } window.addEventListener ("resize", resizeHandler, false); } function getNode (id) { switch (id) { case "window": return window; case "document": return document; case "document.body": const bodyNode = document.getElementById ("ooui-body"); return bodyNode || document.body; default: return nodes[id]; } } function getOrCreateElement (id, tagName) { var e = document.getElementById (id); if (e) { if (e.firstChild && e.firstChild.nodeType == Node.TEXT_NODE) hasText[e.id] = true; return e; } return document.createElement (tagName); } function msgCreate (m) { const id = m.id; const tagName = m.k; const node = tagName === "#text" ? document.createTextNode ("") : getOrCreateElement (id, tagName); if (tagName !== "#text") node.id = id; nodes[id] = node; if (debug) console.log ("Created node", node); } function msgSet (m) { const id = m.id; const node = getNode (id); if (!node) { console.error ("Unknown node id", m); return; } const parts = m.k.split("."); let o = node; for (let i = 0; i < parts.length - 1; i++) { o = o[parts[i]]; } const lastPart = parts[parts.length - 1]; const value = lastPart === "htmlFor" ? m.v.id : m.v; o[lastPart] = value; if (debug) console.log ("Set", node, parts, value); } function msgSetAttr (m) { const id = m.id; const node = getNode (id); if (!node) { console.error ("Unknown node id", m); return; } node.setAttribute(m.k, m.v); if (debug) console.log ("SetAttr", node, m.k, m.v); } function msgRemAttr (m) { const id = m.id; const node = getNode (id); if (!node) { console.error ("Unknown node id", m); return; } node.removeAttribute(m.k); if (debug) console.log ("RemAttr", node, m.k); } function msgCall (m) { const id = m.id; const node = getNode (id); if (!node) { console.error ("Unknown node id", m); return; } const target = node; if (m.k === "insertBefore" && m.v[0].nodeType == Node.TEXT_NODE && m.v[1] == null && hasText[id]) { // Text is already set so it clear it first if (target.firstChild) target.removeChild (target.firstChild); delete hasText[id]; } const f = target[m.k]; if (debug) console.log ("Call", node, f, m.v); const r = f.apply (target, m.v); if (typeof m.rid === 'string' || m.rid instanceof String) { nodes[m.rid] = r; } } function msgListen (m) { const node = getNode (m.id); if (!node) { console.error ("Unknown node id", m); return; } if (debug) console.log ("Listen", node, m.k); node.addEventListener(m.k, function (e) { const em = { m: "event", id: m.id, k: m.k, }; if (m.k === "change" || m.k === "input") { em.v = (node.tagName === "INPUT" && node.type === "checkbox") ? node.checked : node.value; } else if (mouseEvents[m.k]) { em.v = { offsetX: e.offsetX, offsetY: e.offsetY, }; } const ems = JSON.stringify (em); if (socket != null) socket.send (ems); if (debug) console.log ("Event", em); if (em.k === "submit") e.preventDefault (); }); } function processMessage (m) { switch (m.m) { case "nop": break; case "create": msgCreate (m); break; case "set": msgSet (m); break; case "setAttr": msgSetAttr (m); break; case "remAttr": msgRemAttr (m); break; case "call": msgCall (m); break; case "listen": msgListen (m); break; default: console.error ("Unknown message type", m.m, m); } } function fixupValue (v) { if (Array.isArray (v)) { for (x in v) { v[x] = fixupValue (v[x]); } return v; } else if (typeof v === 'string' || v instanceof String) { if ((v.length > 1) && (v[0] === "\u2999")) { // console.log("V", v); return getNode (v); } } return v; } // == WASM Support == var Module = { onRuntimeInitialized: function () { console.log ("Done with WASM module instantiation."); Module.FS_createPath ("/", "managed", true, true); var pending = 0; this.assemblies.forEach (function(asm_name) { console.log ("Loading", asm_name); ++pending; fetch ("managed/" + asm_name, { credentials: 'same-origin' }).then (function (response) { if (!response.ok) throw "failed to load Assembly '" + asm_name + "'"; return response['arrayBuffer'](); }).then (function (blob) { var asm = new Uint8Array (blob); Module.FS_createDataFile ("managed/" + asm_name, null, asm, true, true, true); --pending; if (pending == 0) Module.bclLoadingDone (); }); }); }, bclLoadingDone: function () { console.log ("Done loading the BCL."); MonoRuntime.init (); } }; var MonoRuntime = { init: function () { this.load_runtime = Module.cwrap ('mono_wasm_load_runtime', null, ['string', 'number']); this.assembly_load = Module.cwrap ('mono_wasm_assembly_load', 'number', ['string']); this.find_class = Module.cwrap ('mono_wasm_assembly_find_class', 'number', ['number', 'string', 'string']); this.find_method = Module.cwrap ('mono_wasm_assembly_find_method', 'number', ['number', 'string', 'number']); this.invoke_method = Module.cwrap ('mono_wasm_invoke_method', 'number', ['number', 'number', 'number']); this.mono_string_get_utf8 = Module.cwrap ('mono_wasm_string_get_utf8', 'number', ['number']); this.mono_string = Module.cwrap ('mono_wasm_string_from_js', 'number', ['string']); this.load_runtime ("managed", 1); console.log ("Done initializing the runtime."); WebAssemblyApp.init (); }, conv_string: function (mono_obj) { if (mono_obj == 0) return null; var raw = this.mono_string_get_utf8 (mono_obj); var res = Module.UTF8ToString (raw); Module._free (raw); return res; }, call_method: function (method, this_arg, args) { var args_mem = Module._malloc (args.length * 4); var eh_throw = Module._malloc (4); for (var i = 0; i < args.length; ++i) Module.setValue (args_mem + i * 4, args [i], "i32"); Module.setValue (eh_throw, 0, "i32"); var res = this.invoke_method (method, this_arg, args_mem, eh_throw); var eh_res = Module.getValue (eh_throw, "i32"); Module._free (args_mem); Module._free (eh_throw); if (eh_res != 0) { var msg = this.conv_string (res); throw new Error (msg); } return res; }, }; var WebAssemblyApp = { init: function () { this.loading = document.getElementById ("loading"); this.output = document.getElementById ("output"); this.findMethods (); var res = this.runApp ("1", "2"); this.output.value = res; this.output.hidden = false; this.loading.hidden = true; }, runApp: function (a, b) { try { MonoRuntime.call_method (this.ooui_method, null, [MonoRuntime.mono_string ("main"), MonoRuntime.mono_string ("main")]); var res = MonoRuntime.call_method (this.add_method, null, [MonoRuntime.mono_string (a), MonoRuntime.mono_string (b)]); return MonoRuntime.conv_string (res); } catch (e) { return e.msg; } }, findMethods: function () { this.ooui_module = MonoRuntime.assembly_load ("Ooui") if (!this.ooui_module) throw "Could not find Ooui.dll"; this.ooui_class = MonoRuntime.find_class (this.ooui_module, "Ooui", "UI") if (!this.ooui_class) throw "Could not find UI class in Ooui module"; this.ooui_method = MonoRuntime.find_method (this.ooui_class, "StartWebAssemblySession", -1) if (!this.ooui_method) throw "Could not find StartWebAssemblySession method"; this.main_module = MonoRuntime.assembly_load (mainAsmName) if (!this.main_module) throw "Could not find Main Module " + mainAsmName + ".dll"; this.math_class = MonoRuntime.find_class (this.main_module, "", "Program") if (!this.math_class) throw "Could not find Program class in main module"; this.add_method = MonoRuntime.find_method (this.math_class, "Main", -1) if (!this.add_method) throw "Could not find Main method"; }, };