mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-10 06:34:20 +00:00
Web works with the js structure
This commit is contained in:
parent
16b38e339d
commit
b751674354
16 changed files with 470 additions and 325 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -8527,7 +8527,6 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "sledgehammer_bindgen"
|
||||
version = "0.4.0"
|
||||
source = "git+https://github.com/ealmloff/sledgehammer_bindgen#6f90290e1655014f22623eb8b2ea41fb098e1164"
|
||||
dependencies = [
|
||||
"sledgehammer_bindgen_macro",
|
||||
"wasm-bindgen",
|
||||
|
@ -8536,7 +8535,6 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "sledgehammer_bindgen_macro"
|
||||
version = "0.4.0"
|
||||
source = "git+https://github.com/ealmloff/sledgehammer_bindgen#6f90290e1655014f22623eb8b2ea41fb098e1164"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.51",
|
||||
|
|
|
@ -17,7 +17,8 @@ web-sys = { version = "0.3.56", optional = true, features = [
|
|||
"Element",
|
||||
"Node",
|
||||
] }
|
||||
sledgehammer_bindgen = { git = "https://github.com/ealmloff/sledgehammer_bindgen", default-features = false, optional = true }
|
||||
sledgehammer_bindgen = { path = "../../../Tinkering/sledgehammer_bindgen", default-features = false, optional = true }
|
||||
# sledgehammer_bindgen = { git = "https://github.com/ealmloff/sledgehammer_bindgen", default-features = false, optional = true }
|
||||
sledgehammer_utils = { version = "0.2", optional = true }
|
||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||
|
||||
|
|
|
@ -20,8 +20,9 @@ fn main() {
|
|||
|
||||
// Otherwise, generate the bindings and write the new hash to disk
|
||||
// Generate the bindings for both native and web
|
||||
gen_bindings("interpreter_native", "native");
|
||||
gen_bindings("interpreter_web", "web");
|
||||
gen_bindings("common", "common");
|
||||
gen_bindings("native", "native");
|
||||
gen_bindings("core", "core");
|
||||
|
||||
std::fs::write("src/js/hash.txt", hash.to_string()).unwrap();
|
||||
}
|
||||
|
|
99
packages/interpreter/src/js/common.js
Normal file
99
packages/interpreter/src/js/common.js
Normal file
|
@ -0,0 +1,99 @@
|
|||
// src/ts/set_attribute.ts
|
||||
function setAttributeInner(node, field, value, ns) {
|
||||
if (ns === "style") {
|
||||
node.style.setProperty(field, value);
|
||||
return;
|
||||
}
|
||||
if (!!ns) {
|
||||
node.setAttributeNS(ns, field, value);
|
||||
return;
|
||||
}
|
||||
switch (field) {
|
||||
case "value":
|
||||
if (node.value !== value) {
|
||||
node.value = value;
|
||||
}
|
||||
break;
|
||||
case "initial_value":
|
||||
node.defaultValue = value;
|
||||
break;
|
||||
case "checked":
|
||||
node.checked = truthy(value);
|
||||
break;
|
||||
case "initial_checked":
|
||||
node.defaultChecked = truthy(value);
|
||||
break;
|
||||
case "selected":
|
||||
node.selected = truthy(value);
|
||||
break;
|
||||
case "initial_selected":
|
||||
node.defaultSelected = truthy(value);
|
||||
break;
|
||||
case "dangerous_inner_html":
|
||||
node.innerHTML = value;
|
||||
break;
|
||||
default:
|
||||
if (!truthy(value) && isBoolAttr(field)) {
|
||||
node.removeAttribute(field);
|
||||
} else {
|
||||
node.setAttribute(field, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
var truthy = function(val) {
|
||||
return val === "true" || val === true;
|
||||
};
|
||||
var isBoolAttr = function(field) {
|
||||
switch (field) {
|
||||
case "allowfullscreen":
|
||||
case "allowpaymentrequest":
|
||||
case "async":
|
||||
case "autofocus":
|
||||
case "autoplay":
|
||||
case "checked":
|
||||
case "controls":
|
||||
case "default":
|
||||
case "defer":
|
||||
case "disabled":
|
||||
case "formnovalidate":
|
||||
case "hidden":
|
||||
case "ismap":
|
||||
case "itemscope":
|
||||
case "loop":
|
||||
case "multiple":
|
||||
case "muted":
|
||||
case "nomodule":
|
||||
case "novalidate":
|
||||
case "open":
|
||||
case "playsinline":
|
||||
case "readonly":
|
||||
case "required":
|
||||
case "reversed":
|
||||
case "selected":
|
||||
case "truespeed":
|
||||
case "webkitdirectory":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
// src/ts/form.ts
|
||||
function retrieveFormValues(form) {
|
||||
const formData = new FormData(form);
|
||||
const contents = {};
|
||||
formData.forEach((value, key) => {
|
||||
if (contents[key]) {
|
||||
contents[key] += "," + value;
|
||||
} else {
|
||||
contents[key] = value;
|
||||
}
|
||||
});
|
||||
return {
|
||||
valid: form.checkValidity(),
|
||||
values: contents
|
||||
};
|
||||
}
|
||||
export {
|
||||
setAttributeInner,
|
||||
retrieveFormValues
|
||||
};
|
|
@ -1,81 +1,3 @@
|
|||
// src/ts/interpreter_core.ts
|
||||
class Interpreter {
|
||||
global;
|
||||
local;
|
||||
root;
|
||||
handler;
|
||||
nodes;
|
||||
stack;
|
||||
templates;
|
||||
constructor(root, handler) {
|
||||
this.handler = handler;
|
||||
this.initialize(root);
|
||||
}
|
||||
initialize(root, handler = null) {
|
||||
this.global = {};
|
||||
this.local = {};
|
||||
this.root = root;
|
||||
this.nodes = [root];
|
||||
this.stack = [root];
|
||||
this.templates = {};
|
||||
if (handler) {
|
||||
this.handler = handler;
|
||||
}
|
||||
}
|
||||
createListener(event_name, element, bubbles) {
|
||||
if (bubbles) {
|
||||
if (this.global[event_name] === undefined) {
|
||||
this.global[event_name] = { active: 1, callback: this.handler };
|
||||
this.root.addEventListener(event_name, this.handler);
|
||||
} else {
|
||||
this.global[event_name].active++;
|
||||
}
|
||||
} else {
|
||||
const id = element.getAttribute("data-dioxus-id");
|
||||
if (!this.local[id]) {
|
||||
this.local[id] = {};
|
||||
}
|
||||
element.addEventListener(event_name, this.handler);
|
||||
}
|
||||
}
|
||||
removeListener(element, event_name, bubbles) {
|
||||
if (bubbles) {
|
||||
this.removeBubblingListener(event_name);
|
||||
} else {
|
||||
this.removeNonBubblingListener(element, event_name);
|
||||
}
|
||||
}
|
||||
removeBubblingListener(event_name) {
|
||||
this.global[event_name].active--;
|
||||
if (this.global[event_name].active === 0) {
|
||||
this.root.removeEventListener(event_name, this.global[event_name].callback);
|
||||
delete this.global[event_name];
|
||||
}
|
||||
}
|
||||
removeNonBubblingListener(element, event_name) {
|
||||
const id = element.getAttribute("data-dioxus-id");
|
||||
delete this.local[id][event_name];
|
||||
if (Object.keys(this.local[id]).length === 0) {
|
||||
delete this.local[id];
|
||||
}
|
||||
element.removeEventListener(event_name, this.handler);
|
||||
}
|
||||
removeAllNonBubblingListeners(element) {
|
||||
const id = element.getAttribute("data-dioxus-id");
|
||||
delete this.local[id];
|
||||
}
|
||||
getNode(id) {
|
||||
return this.nodes[id];
|
||||
}
|
||||
appendChildren(id, many) {
|
||||
const root = this.nodes[id];
|
||||
const els = this.stack.splice(this.stack.length - many);
|
||||
for (let k = 0;k < many; k++) {
|
||||
root.appendChild(els[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// src/ts/set_attribute.ts
|
||||
function setAttributeInner(node, field, value, ns) {
|
||||
if (ns === "style") {
|
||||
|
@ -156,13 +78,84 @@ var isBoolAttr = function(field) {
|
|||
}
|
||||
};
|
||||
|
||||
// src/ts/interpreter_web.ts
|
||||
class PlatformInterpreter extends Interpreter {
|
||||
// src/ts/core.ts
|
||||
class BaseInterpreter {
|
||||
global;
|
||||
local;
|
||||
root;
|
||||
handler;
|
||||
nodes;
|
||||
stack;
|
||||
templates;
|
||||
m;
|
||||
constructor(root, handler) {
|
||||
super(root, handler);
|
||||
this.handler = handler;
|
||||
this.initialize(root);
|
||||
}
|
||||
LoadChild(ptr, len) {
|
||||
initialize(root, handler = null) {
|
||||
this.global = {};
|
||||
this.local = {};
|
||||
this.root = root;
|
||||
this.nodes = [root];
|
||||
this.stack = [root];
|
||||
this.templates = {};
|
||||
if (handler) {
|
||||
this.handler = handler;
|
||||
}
|
||||
}
|
||||
createListener(event_name, element, bubbles) {
|
||||
if (bubbles) {
|
||||
if (this.global[event_name] === undefined) {
|
||||
this.global[event_name] = { active: 1, callback: this.handler };
|
||||
this.root.addEventListener(event_name, this.handler);
|
||||
} else {
|
||||
this.global[event_name].active++;
|
||||
}
|
||||
} else {
|
||||
const id = element.getAttribute("data-dioxus-id");
|
||||
if (!this.local[id]) {
|
||||
this.local[id] = {};
|
||||
}
|
||||
element.addEventListener(event_name, this.handler);
|
||||
}
|
||||
}
|
||||
removeListener(element, event_name, bubbles) {
|
||||
if (bubbles) {
|
||||
this.removeBubblingListener(event_name);
|
||||
} else {
|
||||
this.removeNonBubblingListener(element, event_name);
|
||||
}
|
||||
}
|
||||
removeBubblingListener(event_name) {
|
||||
this.global[event_name].active--;
|
||||
if (this.global[event_name].active === 0) {
|
||||
this.root.removeEventListener(event_name, this.global[event_name].callback);
|
||||
delete this.global[event_name];
|
||||
}
|
||||
}
|
||||
removeNonBubblingListener(element, event_name) {
|
||||
const id = element.getAttribute("data-dioxus-id");
|
||||
delete this.local[id][event_name];
|
||||
if (Object.keys(this.local[id]).length === 0) {
|
||||
delete this.local[id];
|
||||
}
|
||||
element.removeEventListener(event_name, this.handler);
|
||||
}
|
||||
removeAllNonBubblingListeners(element) {
|
||||
const id = element.getAttribute("data-dioxus-id");
|
||||
delete this.local[id];
|
||||
}
|
||||
getNode(id) {
|
||||
return this.nodes[id];
|
||||
}
|
||||
appendChildren(id, many) {
|
||||
const root = this.nodes[id];
|
||||
const els = this.stack.splice(this.stack.length - many);
|
||||
for (let k = 0;k < many; k++) {
|
||||
root.appendChild(els[k]);
|
||||
}
|
||||
}
|
||||
loadChild(ptr, len) {
|
||||
let node = this.stack[this.stack.length - 1];
|
||||
let ptr_end = ptr + len;
|
||||
for (;ptr < ptr_end; ptr++) {
|
||||
|
@ -207,8 +200,10 @@ class PlatformInterpreter extends Interpreter {
|
|||
currentNode = treeWalker.nextNode();
|
||||
}
|
||||
}
|
||||
setAttributeInner(node, field, value, ns) {
|
||||
setAttributeInner(node, field, value, ns);
|
||||
}
|
||||
}
|
||||
export {
|
||||
setAttributeInner,
|
||||
PlatformInterpreter
|
||||
BaseInterpreter
|
||||
};
|
|
@ -1 +1 @@
|
|||
3599957386864841107
|
||||
7906913262979346544
|
|
@ -1,5 +1,85 @@
|
|||
// src/ts/interpreter_core.ts
|
||||
class Interpreter {
|
||||
// src/ts/set_attribute.ts
|
||||
function setAttributeInner(node, field, value, ns) {
|
||||
if (ns === "style") {
|
||||
node.style.setProperty(field, value);
|
||||
return;
|
||||
}
|
||||
if (!!ns) {
|
||||
node.setAttributeNS(ns, field, value);
|
||||
return;
|
||||
}
|
||||
switch (field) {
|
||||
case "value":
|
||||
if (node.value !== value) {
|
||||
node.value = value;
|
||||
}
|
||||
break;
|
||||
case "initial_value":
|
||||
node.defaultValue = value;
|
||||
break;
|
||||
case "checked":
|
||||
node.checked = truthy(value);
|
||||
break;
|
||||
case "initial_checked":
|
||||
node.defaultChecked = truthy(value);
|
||||
break;
|
||||
case "selected":
|
||||
node.selected = truthy(value);
|
||||
break;
|
||||
case "initial_selected":
|
||||
node.defaultSelected = truthy(value);
|
||||
break;
|
||||
case "dangerous_inner_html":
|
||||
node.innerHTML = value;
|
||||
break;
|
||||
default:
|
||||
if (!truthy(value) && isBoolAttr(field)) {
|
||||
node.removeAttribute(field);
|
||||
} else {
|
||||
node.setAttribute(field, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
var truthy = function(val) {
|
||||
return val === "true" || val === true;
|
||||
};
|
||||
var isBoolAttr = function(field) {
|
||||
switch (field) {
|
||||
case "allowfullscreen":
|
||||
case "allowpaymentrequest":
|
||||
case "async":
|
||||
case "autofocus":
|
||||
case "autoplay":
|
||||
case "checked":
|
||||
case "controls":
|
||||
case "default":
|
||||
case "defer":
|
||||
case "disabled":
|
||||
case "formnovalidate":
|
||||
case "hidden":
|
||||
case "ismap":
|
||||
case "itemscope":
|
||||
case "loop":
|
||||
case "multiple":
|
||||
case "muted":
|
||||
case "nomodule":
|
||||
case "novalidate":
|
||||
case "open":
|
||||
case "playsinline":
|
||||
case "readonly":
|
||||
case "required":
|
||||
case "reversed":
|
||||
case "selected":
|
||||
case "truespeed":
|
||||
case "webkitdirectory":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// src/ts/core.ts
|
||||
class BaseInterpreter {
|
||||
global;
|
||||
local;
|
||||
root;
|
||||
|
@ -7,6 +87,7 @@ class Interpreter {
|
|||
nodes;
|
||||
stack;
|
||||
templates;
|
||||
m;
|
||||
constructor(root, handler) {
|
||||
this.handler = handler;
|
||||
this.initialize(root);
|
||||
|
@ -74,6 +155,54 @@ class Interpreter {
|
|||
root.appendChild(els[k]);
|
||||
}
|
||||
}
|
||||
loadChild(ptr, len) {
|
||||
let node = this.stack[this.stack.length - 1];
|
||||
let ptr_end = ptr + len;
|
||||
for (;ptr < ptr_end; ptr++) {
|
||||
let end = this.m.getUint8(ptr);
|
||||
for (node = node.firstChild;end > 0; end--) {
|
||||
node = node.nextSibling;
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
saveTemplate(nodes, tmpl_id) {
|
||||
this.templates[tmpl_id] = nodes;
|
||||
}
|
||||
hydrateRoot(ids) {
|
||||
const hydrateNodes = document.querySelectorAll("[data-node-hydration]");
|
||||
for (let i = 0;i < hydrateNodes.length; i++) {
|
||||
const hydrateNode = hydrateNodes[i];
|
||||
const hydration = hydrateNode.getAttribute("data-node-hydration");
|
||||
const split = hydration.split(",");
|
||||
const id = ids[parseInt(split[0])];
|
||||
this.nodes[id] = hydrateNode;
|
||||
if (split.length > 1) {
|
||||
hydrateNode.listening = split.length - 1;
|
||||
hydrateNode.setAttribute("data-dioxus-id", id.toString());
|
||||
for (let j = 1;j < split.length; j++) {
|
||||
const listener = split[j];
|
||||
const split2 = listener.split(":");
|
||||
const event_name = split2[0];
|
||||
const bubbles = split2[1] === "1";
|
||||
this.createListener(event_name, hydrateNode, bubbles);
|
||||
}
|
||||
}
|
||||
}
|
||||
const treeWalker = document.createTreeWalker(document.body, NodeFilter.SHOW_COMMENT);
|
||||
let currentNode = treeWalker.nextNode();
|
||||
while (currentNode) {
|
||||
const id = currentNode.textContent;
|
||||
const split = id.split("node-id");
|
||||
if (split.length > 1) {
|
||||
this.nodes[ids[parseInt(split[1])]] = currentNode.nextSibling;
|
||||
}
|
||||
currentNode = treeWalker.nextNode();
|
||||
}
|
||||
}
|
||||
setAttributeInner(node, field, value, ns) {
|
||||
setAttributeInner(node, field, value, ns);
|
||||
}
|
||||
}
|
||||
|
||||
// src/ts/form.ts
|
||||
|
@ -291,87 +420,7 @@ var serializeDragEvent = function(event) {
|
|||
};
|
||||
};
|
||||
|
||||
// src/ts/set_attribute.ts
|
||||
function setAttributeInner(node, field, value, ns) {
|
||||
if (ns === "style") {
|
||||
node.style.setProperty(field, value);
|
||||
return;
|
||||
}
|
||||
if (!!ns) {
|
||||
node.setAttributeNS(ns, field, value);
|
||||
return;
|
||||
}
|
||||
switch (field) {
|
||||
case "value":
|
||||
if (node.value !== value) {
|
||||
node.value = value;
|
||||
}
|
||||
break;
|
||||
case "initial_value":
|
||||
node.defaultValue = value;
|
||||
break;
|
||||
case "checked":
|
||||
node.checked = truthy(value);
|
||||
break;
|
||||
case "initial_checked":
|
||||
node.defaultChecked = truthy(value);
|
||||
break;
|
||||
case "selected":
|
||||
node.selected = truthy(value);
|
||||
break;
|
||||
case "initial_selected":
|
||||
node.defaultSelected = truthy(value);
|
||||
break;
|
||||
case "dangerous_inner_html":
|
||||
node.innerHTML = value;
|
||||
break;
|
||||
default:
|
||||
if (!truthy(value) && isBoolAttr(field)) {
|
||||
node.removeAttribute(field);
|
||||
} else {
|
||||
node.setAttribute(field, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
var truthy = function(val) {
|
||||
return val === "true" || val === true;
|
||||
};
|
||||
var isBoolAttr = function(field) {
|
||||
switch (field) {
|
||||
case "allowfullscreen":
|
||||
case "allowpaymentrequest":
|
||||
case "async":
|
||||
case "autofocus":
|
||||
case "autoplay":
|
||||
case "checked":
|
||||
case "controls":
|
||||
case "default":
|
||||
case "defer":
|
||||
case "disabled":
|
||||
case "formnovalidate":
|
||||
case "hidden":
|
||||
case "ismap":
|
||||
case "itemscope":
|
||||
case "loop":
|
||||
case "multiple":
|
||||
case "muted":
|
||||
case "nomodule":
|
||||
case "novalidate":
|
||||
case "open":
|
||||
case "playsinline":
|
||||
case "readonly":
|
||||
case "required":
|
||||
case "reversed":
|
||||
case "selected":
|
||||
case "truespeed":
|
||||
case "webkitdirectory":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// src/ts/interpreter_native.ts
|
||||
// src/ts/native.ts
|
||||
var targetId = function(target) {
|
||||
if (!(target instanceof Node)) {
|
||||
return null;
|
||||
|
@ -390,7 +439,7 @@ var targetId = function(target) {
|
|||
return parseInt(realId);
|
||||
};
|
||||
|
||||
class PlatformInterpreter extends Interpreter {
|
||||
class PlatformInterpreter extends BaseInterpreter {
|
||||
intercept_link_redirects;
|
||||
ipc;
|
||||
liveview;
|
||||
|
|
|
@ -2,8 +2,11 @@
|
|||
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
|
||||
pub static INTERPRETER_JS: &str = include_str!("./js/native.js");
|
||||
pub static COMMON_JS: &str = include_str!("./js/web.js");
|
||||
/// The base class that the JS channel will extend
|
||||
pub static INTERPRETER_JS: &str = include_str!("./js/core.js");
|
||||
|
||||
/// The code explicitly for desktop/liveview that bridges the eval gap between the two
|
||||
pub static NATIVE_JS: &str = include_str!("./js/native.js");
|
||||
|
||||
#[cfg(all(feature = "binary-protocol", feature = "sledgehammer"))]
|
||||
mod write_native_mutations;
|
||||
|
@ -25,7 +28,7 @@ pub mod minimal_bindings {
|
|||
/// Some useful snippets that we use to share common functionality between the different platforms we support.
|
||||
///
|
||||
/// This maintains some sort of consistency between web, desktop, and liveview
|
||||
#[wasm_bindgen(module = "/src/js/web.js")]
|
||||
#[wasm_bindgen(module = "/src/js/common.js")]
|
||||
extern "C" {
|
||||
/// Set the attribute of the node
|
||||
pub fn setAttributeInner(node: JsValue, name: &str, value: JsValue, ns: Option<&str>);
|
||||
|
|
2
packages/interpreter/src/ts/common.ts
Normal file
2
packages/interpreter/src/ts/common.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export { setAttributeInner } from "./set_attribute";
|
||||
export { retrieveFormValues } from "./form";
|
|
@ -1,9 +1,11 @@
|
|||
// The root interpreter class that holds state about the mapping between DOM and VirtualDom
|
||||
// This always lives in the JS side of things, and is extended by the native and web interpreters
|
||||
|
||||
import { setAttributeInner } from "./set_attribute";
|
||||
|
||||
export type NodeId = number;
|
||||
|
||||
export class Interpreter {
|
||||
export class BaseInterpreter {
|
||||
// non bubbling events listen at the element the listener was created at
|
||||
global: {
|
||||
[key: string]: { active: number, callback: EventListener }
|
||||
|
@ -23,6 +25,9 @@ export class Interpreter {
|
|||
[key: string]: Node[]
|
||||
};
|
||||
|
||||
// sledgehammer is generating this...
|
||||
m: any;
|
||||
|
||||
constructor(root: HTMLElement, handler: EventListener) {
|
||||
this.handler = handler;
|
||||
this.initialize(root);
|
||||
|
@ -100,5 +105,72 @@ export class Interpreter {
|
|||
root.appendChild(els[k]);
|
||||
}
|
||||
}
|
||||
|
||||
loadChild(ptr: number, len: number): Node {
|
||||
// iterate through each number and get that child
|
||||
let node = this.stack[this.stack.length - 1] as Node;
|
||||
let ptr_end = ptr + len;
|
||||
|
||||
for (; ptr < ptr_end; ptr++) {
|
||||
let end = this.m.getUint8(ptr);
|
||||
for (node = node.firstChild; end > 0; end--) {
|
||||
node = node.nextSibling;
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
saveTemplate(nodes: HTMLElement[], tmpl_id: string) {
|
||||
this.templates[tmpl_id] = nodes;
|
||||
}
|
||||
|
||||
hydrateRoot(ids: { [key: number]: number }) {
|
||||
const hydrateNodes = document.querySelectorAll('[data-node-hydration]');
|
||||
|
||||
for (let i = 0; i < hydrateNodes.length; i++) {
|
||||
const hydrateNode = hydrateNodes[i] as HTMLElement;
|
||||
const hydration = hydrateNode.getAttribute('data-node-hydration');
|
||||
const split = hydration!.split(',');
|
||||
const id = ids[parseInt(split[0])];
|
||||
|
||||
this.nodes[id] = hydrateNode;
|
||||
|
||||
if (split.length > 1) {
|
||||
// @ts-ignore
|
||||
hydrateNode.listening = split.length - 1;
|
||||
hydrateNode.setAttribute('data-dioxus-id', id.toString());
|
||||
for (let j = 1; j < split.length; j++) {
|
||||
const listener = split[j];
|
||||
const split2 = listener.split(':');
|
||||
const event_name = split2[0];
|
||||
const bubbles = split2[1] === '1';
|
||||
this.createListener(event_name, hydrateNode, bubbles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const treeWalker = document.createTreeWalker(
|
||||
document.body,
|
||||
NodeFilter.SHOW_COMMENT,
|
||||
);
|
||||
|
||||
let currentNode = treeWalker.nextNode();
|
||||
|
||||
while (currentNode) {
|
||||
const id = currentNode.textContent!;
|
||||
const split = id.split('node-id');
|
||||
|
||||
if (split.length > 1) {
|
||||
this.nodes[ids[parseInt(split[1])]] = currentNode.nextSibling;
|
||||
}
|
||||
|
||||
currentNode = treeWalker.nextNode();
|
||||
}
|
||||
}
|
||||
|
||||
setAttributeInner(node: HTMLElement, field: string, value: string, ns: string) {
|
||||
setAttributeInner(node, field, value, ns);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
// The JS<->Rust bridge
|
||||
// This file is thin, suitable just for the web
|
||||
// If you want the more full-featured intrepreter, look at the native interpreter which extends this with additional functionality
|
||||
//
|
||||
// We're using sledgehammer directly
|
||||
|
||||
import { Interpreter } from "./interpreter_core";
|
||||
export { setAttributeInner } from "./set_attribute";
|
||||
|
||||
export class PlatformInterpreter extends Interpreter {
|
||||
m: any;
|
||||
|
||||
constructor(root: HTMLElement, handler: EventListener) {
|
||||
super(root, handler);
|
||||
}
|
||||
|
||||
LoadChild(ptr: number, len: number): Node {
|
||||
// iterate through each number and get that child
|
||||
let node = this.stack[this.stack.length - 1] as Node;
|
||||
let ptr_end = ptr + len;
|
||||
|
||||
for (; ptr < ptr_end; ptr++) {
|
||||
let end = this.m.getUint8(ptr);
|
||||
for (node = node.firstChild; end > 0; end--) {
|
||||
node = node.nextSibling;
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
saveTemplate(nodes: HTMLElement[], tmpl_id: string) {
|
||||
this.templates[tmpl_id] = nodes;
|
||||
}
|
||||
|
||||
hydrateRoot(ids: { [key: number]: number }) {
|
||||
const hydrateNodes = document.querySelectorAll('[data-node-hydration]');
|
||||
|
||||
for (let i = 0; i < hydrateNodes.length; i++) {
|
||||
const hydrateNode = hydrateNodes[i] as HTMLElement;
|
||||
const hydration = hydrateNode.getAttribute('data-node-hydration');
|
||||
const split = hydration!.split(',');
|
||||
const id = ids[parseInt(split[0])];
|
||||
|
||||
this.nodes[id] = hydrateNode;
|
||||
|
||||
if (split.length > 1) {
|
||||
// @ts-ignore
|
||||
hydrateNode.listening = split.length - 1;
|
||||
hydrateNode.setAttribute('data-dioxus-id', id.toString());
|
||||
for (let j = 1; j < split.length; j++) {
|
||||
const listener = split[j];
|
||||
const split2 = listener.split(':');
|
||||
const event_name = split2[0];
|
||||
const bubbles = split2[1] === '1';
|
||||
this.createListener(event_name, hydrateNode, bubbles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const treeWalker = document.createTreeWalker(
|
||||
document.body,
|
||||
NodeFilter.SHOW_COMMENT,
|
||||
);
|
||||
|
||||
let currentNode = treeWalker.nextNode();
|
||||
|
||||
while (currentNode) {
|
||||
const id = currentNode.textContent!;
|
||||
const split = id.split('node-id');
|
||||
|
||||
if (split.length > 1) {
|
||||
this.nodes[ids[parseInt(split[1])]] = currentNode.nextSibling;
|
||||
}
|
||||
|
||||
currentNode = treeWalker.nextNode();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,11 +3,11 @@
|
|||
// This file lives on the renderer, not the host. It's basically a polyfill over functionality that the host can't
|
||||
// provide since it doesn't have access to the dom.
|
||||
|
||||
import { Interpreter, NodeId } from "./interpreter_core";
|
||||
import { BaseInterpreter, NodeId } from "./core";
|
||||
import { SerializedEvent, serializeEvent } from "./serialize";
|
||||
import { setAttributeInner } from "./set_attribute";
|
||||
|
||||
export class PlatformInterpreter extends Interpreter {
|
||||
export class PlatformInterpreter extends BaseInterpreter {
|
||||
intercept_link_redirects: boolean;
|
||||
ipc: any;
|
||||
|
|
@ -1,32 +1,54 @@
|
|||
#[cfg(feature = "webonly")]
|
||||
use web_sys::Node;
|
||||
|
||||
#[cfg(feature = "webonly")]
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
|
||||
use sledgehammer_bindgen::bindgen;
|
||||
|
||||
/// Combine the interpreter class with the sledgehammer_bindgen generated methods.
|
||||
pub fn native_js() -> String {
|
||||
format!("{}\n{}", include_str!("./js/native.js"), GENERATED_JS,)
|
||||
}
|
||||
|
||||
pub const SLEDGEHAMMER_JS: &str = GENERATED_JS;
|
||||
|
||||
#[bindgen(module)]
|
||||
#[cfg(feature = "webonly")]
|
||||
#[wasm_bindgen::prelude::wasm_bindgen]
|
||||
extern "C" {
|
||||
pub type BaseInterpreter;
|
||||
|
||||
#[wasm_bindgen(method)]
|
||||
pub fn initialize(this: &BaseInterpreter, root: Node, handler: &js_sys::Function);
|
||||
|
||||
#[wasm_bindgen(method, js_name = "saveTemplate")]
|
||||
pub fn save_template(this: &BaseInterpreter, nodes: Vec<Node>, tmpl_id: u16);
|
||||
|
||||
#[wasm_bindgen(method)]
|
||||
pub fn hydrate(this: &BaseInterpreter, ids: Vec<u32>);
|
||||
|
||||
#[wasm_bindgen(method, js_name = "getNode")]
|
||||
pub fn get_node(this: &BaseInterpreter, id: u32) -> Node;
|
||||
}
|
||||
|
||||
// Note that this impl is for the sledgehammer interpreter to allow us dropping down to the base interpreter
|
||||
// During hydration and initialization we need to the base interpreter methods
|
||||
#[cfg(feature = "webonly")]
|
||||
impl Interpreter {
|
||||
/// Convert the interpreter to its baseclass, giving
|
||||
pub fn base(&self) -> &BaseInterpreter {
|
||||
use wasm_bindgen::prelude::JsCast;
|
||||
&self.js_channel().unchecked_ref()
|
||||
}
|
||||
}
|
||||
|
||||
#[sledgehammer_bindgen::bindgen(module)]
|
||||
mod js {
|
||||
// Extend the web base class
|
||||
const BASE: &str = "./src/js/core.js";
|
||||
|
||||
/// The interpreter extends the core interpreter which contains the state for the interpreter along with some functions that all platforms use like `AppendChildren`.
|
||||
#[extends(PlatformInterpreter)]
|
||||
#[extends(BaseInterpreter)]
|
||||
pub struct Interpreter;
|
||||
|
||||
fn mount_to_root() {
|
||||
"{this.AppendChildren(this.root, this.stack.length-1);}"
|
||||
"{this.appendChildren(this.root, this.stack.length-1);}"
|
||||
}
|
||||
fn push_root(root: u32) {
|
||||
"{this.stack.push(this.nodes[$root$]);}"
|
||||
}
|
||||
fn append_children(id: u32, many: u16) {
|
||||
"{this.AppendChildren($id$, $many$);}"
|
||||
"{this.appendChildren($id$, $many$);}"
|
||||
}
|
||||
fn pop_root() {
|
||||
"{this.stack.pop();}"
|
||||
|
@ -93,11 +115,11 @@ mod js {
|
|||
}"#
|
||||
}
|
||||
fn assign_id(ptr: u32, len: u8, id: u32) {
|
||||
"{this.nodes[$id$] = this.LoadChild($ptr$, $len$);}"
|
||||
"{this.nodes[$id$] = this.loadChild($ptr$, $len$);}"
|
||||
}
|
||||
fn hydrate_text(ptr: u32, len: u8, value: &str, id: u32) {
|
||||
r#"{
|
||||
let node = this.LoadChild($ptr$, $len$);
|
||||
let node = this.loadChild($ptr$, $len$);
|
||||
if (node.nodeType == node.TEXT_NODE) {
|
||||
node.textContent = value;
|
||||
} else {
|
||||
|
@ -109,20 +131,13 @@ mod js {
|
|||
}"#
|
||||
}
|
||||
fn replace_placeholder(ptr: u32, len: u8, n: u16) {
|
||||
"{this.els = this.stack.splice(this.stack.length - $n$); let node = this.LoadChild($ptr$, $len$); node.replaceWith(...this.els);}"
|
||||
"{this.els = this.stack.splice(this.stack.length - $n$); let node = this.loadChild($ptr$, $len$); node.replaceWith(...this.els);}"
|
||||
}
|
||||
fn load_template(tmpl_id: u16, index: u16, id: u32) {
|
||||
"{let node = this.templates[$tmpl_id$][$index$].cloneNode(true); this.nodes[$id$] = node; this.stack.push(node);}"
|
||||
}
|
||||
|
||||
/*
|
||||
Binary protocol methods only!
|
||||
|
||||
These methods let us support binary packing mutations for use on boundaries like desktop where we prefer to send
|
||||
binary data instead of JSON.
|
||||
|
||||
We're using native types in a number of places
|
||||
*/
|
||||
#[cfg(feature = "binary-protocol")]
|
||||
fn append_children_to_top(many: u16) {
|
||||
"{
|
||||
let root = this.stack[this.stack.length-many-1];
|
||||
|
@ -132,21 +147,33 @@ mod js {
|
|||
}
|
||||
}"
|
||||
}
|
||||
|
||||
#[cfg(feature = "binary-protocol")]
|
||||
fn set_top_attribute(field: &str<u8, attr>, value: &str, ns: &str<u8, ns_cache>) {
|
||||
"{this.setAttributeInner(this.stack[this.stack.length-1], $field$, $value$, $ns$);}"
|
||||
}
|
||||
|
||||
#[cfg(feature = "binary-protocol")]
|
||||
fn add_placeholder() {
|
||||
"{let node = document.createElement('pre'); node.hidden = true; this.stack.push(node);}"
|
||||
}
|
||||
|
||||
#[cfg(feature = "binary-protocol")]
|
||||
fn create_element(element: &'static str<u8, el>) {
|
||||
"{this.stack.push(document.createElement($element$))}"
|
||||
}
|
||||
|
||||
#[cfg(feature = "binary-protocol")]
|
||||
fn create_element_ns(element: &'static str<u8, el>, ns: &'static str<u8, namespace>) {
|
||||
"{this.stack.push(document.createElementNS($ns$, $element$))}"
|
||||
}
|
||||
|
||||
#[cfg(feature = "binary-protocol")]
|
||||
fn add_templates(tmpl_id: u16, len: u16) {
|
||||
"{this.templates[$tmpl_id$] = this.stack.splice(this.stack.length-$len$);}"
|
||||
}
|
||||
|
||||
#[cfg(feature = "binary-protocol")]
|
||||
fn foreign_event_listener(event: &str<u8, evt>, id: u32, bubbles: u8) {
|
||||
r#"
|
||||
bubbles = bubbles == 1;
|
||||
|
@ -175,12 +202,18 @@ mod js {
|
|||
});
|
||||
}"#
|
||||
}
|
||||
|
||||
/// Assign the ID
|
||||
#[cfg(feature = "binary-protocol")]
|
||||
fn assign_id_ref(array: &[u8], id: u32) {
|
||||
"{this.nodes[$id$] = this.LoadChild($array$);}"
|
||||
"{this.nodes[$id$] = this.loadChild($array$);}"
|
||||
}
|
||||
|
||||
/// The coolest ID ever!
|
||||
#[cfg(feature = "binary-protocol")]
|
||||
fn hydrate_text_ref(array: &[u8], value: &str, id: u32) {
|
||||
r#"{
|
||||
let node = this.LoadChild($array$);
|
||||
let node = this.loadChild($array$);
|
||||
if (node.nodeType == node.TEXT_NODE) {
|
||||
node.textContent = value;
|
||||
} else {
|
||||
|
@ -191,38 +224,9 @@ mod js {
|
|||
this.nodes[$id$] = node;
|
||||
}"#
|
||||
}
|
||||
|
||||
#[cfg(feature = "binary-protocol")]
|
||||
fn replace_placeholder_ref(array: &[u8], n: u16) {
|
||||
"{this.els = this.stack.splice(this.stack.length - $n$); let node = this.LoadChild($array$); node.replaceWith(...this.els);}"
|
||||
}
|
||||
}
|
||||
|
||||
/// Extensions to the interpreter that are specific to the web platform.
|
||||
#[cfg(feature = "webonly")]
|
||||
#[wasm_bindgen(module = "src/js/web.js")]
|
||||
extern "C" {
|
||||
pub type WebInterpreter;
|
||||
|
||||
#[wasm_bindgen(method)]
|
||||
pub fn initialize(this: &WebInterpreter, root: Node, handler: &js_sys::Function);
|
||||
|
||||
#[wasm_bindgen(method, js_name = "saveTemplate")]
|
||||
pub fn save_template(this: &WebInterpreter, nodes: Vec<Node>, tmpl_id: u16);
|
||||
|
||||
#[wasm_bindgen(method)]
|
||||
pub fn hydrate(this: &WebInterpreter, ids: Vec<u32>);
|
||||
|
||||
#[wasm_bindgen(method, js_name = "getNode")]
|
||||
pub fn get_node(this: &WebInterpreter, id: u32) -> Node;
|
||||
}
|
||||
|
||||
#[cfg(feature = "webonly")]
|
||||
type PlatformInterpreter = WebInterpreter;
|
||||
|
||||
#[cfg(feature = "webonly")]
|
||||
impl Interpreter {
|
||||
/// Convert the interpreter to a web interpreter, enabling methods like hydrate and save_template.
|
||||
pub fn as_web(&self) -> &WebInterpreter {
|
||||
use wasm_bindgen::prelude::JsCast;
|
||||
&self.js_channel().unchecked_ref()
|
||||
"{this.els = this.stack.splice(this.stack.length - $n$); let node = this.loadChild($array$); node.replaceWith(...this.els);}"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
use dioxus_core::ElementId;
|
||||
use dioxus_html::PlatformEventData;
|
||||
use dioxus_interpreter_js::{unified_bindings::Interpreter, WebInterpreter};
|
||||
use dioxus_interpreter_js::unified_bindings::Interpreter;
|
||||
use futures_channel::mpsc;
|
||||
use rustc_hash::FxHashMap;
|
||||
use wasm_bindgen::{closure::Closure, JsCast};
|
||||
|
@ -112,7 +112,7 @@ impl WebsysDom {
|
|||
}
|
||||
}));
|
||||
|
||||
let _interpreter: &WebInterpreter = interpreter.as_web();
|
||||
let _interpreter = interpreter.base();
|
||||
_interpreter.initialize(
|
||||
root.clone().unchecked_into(),
|
||||
handler.as_ref().unchecked_ref(),
|
||||
|
|
|
@ -65,7 +65,7 @@ impl WebsysDom {
|
|||
#[cfg(feature = "mounted")]
|
||||
fn flush_queued_mounted_events(&mut self) {
|
||||
for id in self.queued_mounted_events.drain(..) {
|
||||
let node = self.interpreter.as_web().get_node(id.0 as u32);
|
||||
let node = self.interpreter.base().get_node(id.0 as u32);
|
||||
if let Some(element) = node.dyn_ref::<web_sys::Element>() {
|
||||
let _ = self.event_channel.unbounded_send(UiEvent {
|
||||
name: "mounted".to_string(),
|
||||
|
@ -92,7 +92,7 @@ impl WriteMutations for WebsysDom {
|
|||
self.templates
|
||||
.insert(template.name.to_owned(), self.max_template_id);
|
||||
self.interpreter
|
||||
.as_web()
|
||||
.base()
|
||||
.save_template(roots, self.max_template_id);
|
||||
self.max_template_id += 1
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ impl WebsysDom {
|
|||
// Recursively rehydrate the dom from the VirtualDom
|
||||
self.rehydrate_scope(root_scope, dom, &mut ids, &mut to_mount)?;
|
||||
|
||||
self.interpreter.as_web().hydrate(ids);
|
||||
self.interpreter.base().hydrate(ids);
|
||||
|
||||
#[cfg(feature = "mounted")]
|
||||
for id in to_mount {
|
||||
|
@ -168,7 +168,7 @@ impl WriteMutations for OnlyWriteTemplates<'_> {
|
|||
.insert(template.name.to_owned(), self.0.max_template_id);
|
||||
self.0
|
||||
.interpreter
|
||||
.as_web()
|
||||
.base()
|
||||
.save_template(roots, self.0.max_template_id);
|
||||
self.0.max_template_id += 1
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue