Web works with the js structure

This commit is contained in:
Jonathan Kelley 2024-03-05 11:16:34 -08:00
parent 16b38e339d
commit b751674354
No known key found for this signature in database
GPG key ID: 1FBB50F7EB0A08BE
16 changed files with 470 additions and 325 deletions

2
Cargo.lock generated
View file

@ -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",

View file

@ -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 }

View file

@ -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();
}

View 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
};

View file

@ -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
};

View file

@ -1 +1 @@
3599957386864841107
7906913262979346544

View file

@ -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;

View file

@ -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>);

View file

@ -0,0 +1,2 @@
export { setAttributeInner } from "./set_attribute";
export { retrieveFormValues } from "./form";

View file

@ -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);
}
}

View file

@ -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();
}
}
}

View file

@ -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;

View file

@ -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);}"
}
}

View file

@ -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(),

View file

@ -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
}

View file

@ -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
}