switch liveview to sledgehammer
This commit is contained in:
1 parent
8b411b8c6d
commit
f20b740abe
17 files changed
+791
-583
No files matched your search
@@ -14,6 +14,7 @@ use dioxus_core::ScopeState;
|
|||||||
use dioxus_core::VirtualDom;
|
use dioxus_core::VirtualDom;
|
||||||
#[cfg(all(feature = "hot-reload", debug_assertions))]
|
#[cfg(all(feature = "hot-reload", debug_assertions))]
|
||||||
use dioxus_hot_reload::HotReloadMsg;
|
use dioxus_hot_reload::HotReloadMsg;
|
||||||
|
use dioxus_interpreter_js::Channel;
|
||||||
use slab::Slab;
|
use slab::Slab;
|
||||||
use wry::application::accelerator::Accelerator;
|
use wry::application::accelerator::Accelerator;
|
||||||
use wry::application::event::Event;
|
use wry::application::event::Event;
|
||||||
@@ -35,6 +36,18 @@ pub fn use_window(cx: &ScopeState) -> &DesktopContext {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct EditQueue {
|
||||||
|
queue: Vec<Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EditQueue {
|
||||||
|
fn push(&mut self, channel: &mut Channel) {
|
||||||
|
let iter = channel.export_memory();
|
||||||
|
self.queue.push(iter.collect());
|
||||||
|
channel.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) type WebviewQueue = Rc<RefCell<Vec<WebviewHandler>>>;
|
pub(crate) type WebviewQueue = Rc<RefCell<Vec<WebviewHandler>>>;
|
||||||
|
|
||||||
/// An imperative interface to the current window.
|
/// An imperative interface to the current window.
|
||||||
@@ -67,6 +80,10 @@ pub struct DesktopService {
|
|||||||
|
|
||||||
pub(crate) shortcut_manager: ShortcutRegistry,
|
pub(crate) shortcut_manager: ShortcutRegistry,
|
||||||
|
|
||||||
|
pub(crate) event_queue: Rc<RefCell<Vec<Vec<u8>>>>,
|
||||||
|
|
||||||
|
pub(crate) channel: Channel,
|
||||||
|
|
||||||
#[cfg(target_os = "ios")]
|
#[cfg(target_os = "ios")]
|
||||||
pub(crate) views: Rc<RefCell<Vec<*mut objc::runtime::Object>>>,
|
pub(crate) views: Rc<RefCell<Vec<*mut objc::runtime::Object>>>,
|
||||||
}
|
}
|
||||||
@@ -100,6 +117,8 @@ impl DesktopService {
|
|||||||
pending_windows: webviews,
|
pending_windows: webviews,
|
||||||
event_handlers,
|
event_handlers,
|
||||||
shortcut_manager,
|
shortcut_manager,
|
||||||
|
event_queue: Rc::new(RefCell::new(Vec::new())),
|
||||||
|
channel: Channel::new(),
|
||||||
#[cfg(target_os = "ios")]
|
#[cfg(target_os = "ios")]
|
||||||
views: Default::default(),
|
views: Default::default(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,13 +14,13 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
|
|||||||
wasm-bindgen = { workspace = true, optional = true }
|
wasm-bindgen = { workspace = true, optional = true }
|
||||||
js-sys = { version = "0.3.56", optional = true }
|
js-sys = { version = "0.3.56", optional = true }
|
||||||
web-sys = { version = "0.3.56", optional = true, features = ["Element", "Node"] }
|
web-sys = { version = "0.3.56", optional = true, features = ["Element", "Node"] }
|
||||||
sledgehammer_bindgen = { version = "0.2.1", optional = true }
|
sledgehammer_bindgen = { path = "/Users/evanalmloff/Desktop/Github/sledgehammer_bindgen", default-features = false, optional = true }
|
||||||
sledgehammer_utils = { version = "0.2", optional = true }
|
sledgehammer_utils = { version = "0.2", optional = true }
|
||||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
serialize = ["serde"]
|
serialize = ["serde"]
|
||||||
web = ["wasm-bindgen", "js-sys", "web-sys"]
|
sledgehammer = ["sledgehammer_bindgen", "sledgehammer_utils"]
|
||||||
sledgehammer = ["wasm-bindgen", "js-sys", "web-sys", "sledgehammer_bindgen", "sledgehammer_utils"]
|
web = ["sledgehammer", "wasm-bindgen", "js-sys", "web-sys", "sledgehammer_bindgen/web"]
|
||||||
minimal_bindings = []
|
minimal_bindings = []
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
#![allow(clippy::unused_unit, non_upper_case_globals)]
|
|
||||||
|
|
||||||
use js_sys::Function;
|
|
||||||
use wasm_bindgen::prelude::*;
|
|
||||||
use web_sys::Element;
|
|
||||||
|
|
||||||
#[wasm_bindgen(module = "/src/interpreter.js")]
|
|
||||||
extern "C" {
|
|
||||||
pub type InterpreterConfig;
|
|
||||||
#[wasm_bindgen(constructor)]
|
|
||||||
pub fn new(intercept_link_redirects: bool) -> InterpreterConfig;
|
|
||||||
|
|
||||||
pub type Interpreter;
|
|
||||||
|
|
||||||
#[wasm_bindgen(constructor)]
|
|
||||||
pub fn new(arg: Element, config: InterpreterConfig) -> Interpreter;
|
|
||||||
|
|
||||||
#[wasm_bindgen(method)]
|
|
||||||
pub fn SaveTemplate(this: &Interpreter, template: JsValue);
|
|
||||||
|
|
||||||
#[wasm_bindgen(method)]
|
|
||||||
pub fn MountToRoot(this: &Interpreter);
|
|
||||||
|
|
||||||
#[wasm_bindgen(method)]
|
|
||||||
pub fn AssignId(this: &Interpreter, path: &[u8], id: u32);
|
|
||||||
|
|
||||||
#[wasm_bindgen(method)]
|
|
||||||
pub fn CreatePlaceholder(this: &Interpreter, id: u32);
|
|
||||||
|
|
||||||
#[wasm_bindgen(method)]
|
|
||||||
pub fn CreateTextNode(this: &Interpreter, value: JsValue, id: u32);
|
|
||||||
|
|
||||||
#[wasm_bindgen(method)]
|
|
||||||
pub fn HydrateText(this: &Interpreter, path: &[u8], value: &str, id: u32);
|
|
||||||
|
|
||||||
#[wasm_bindgen(method)]
|
|
||||||
pub fn LoadTemplate(this: &Interpreter, name: &str, index: u32, id: u32);
|
|
||||||
|
|
||||||
#[wasm_bindgen(method)]
|
|
||||||
pub fn ReplaceWith(this: &Interpreter, id: u32, m: u32);
|
|
||||||
|
|
||||||
#[wasm_bindgen(method)]
|
|
||||||
pub fn ReplacePlaceholder(this: &Interpreter, path: &[u8], m: u32);
|
|
||||||
|
|
||||||
#[wasm_bindgen(method)]
|
|
||||||
pub fn InsertAfter(this: &Interpreter, id: u32, n: u32);
|
|
||||||
|
|
||||||
#[wasm_bindgen(method)]
|
|
||||||
pub fn InsertBefore(this: &Interpreter, id: u32, n: u32);
|
|
||||||
|
|
||||||
#[wasm_bindgen(method)]
|
|
||||||
pub fn SetAttribute(this: &Interpreter, id: u32, name: &str, value: JsValue, ns: Option<&str>);
|
|
||||||
|
|
||||||
#[wasm_bindgen(method)]
|
|
||||||
pub fn SetBoolAttribute(this: &Interpreter, id: u32, name: &str, value: bool);
|
|
||||||
|
|
||||||
#[wasm_bindgen(method)]
|
|
||||||
pub fn SetText(this: &Interpreter, id: u32, text: JsValue);
|
|
||||||
|
|
||||||
#[wasm_bindgen(method)]
|
|
||||||
pub fn NewEventListener(
|
|
||||||
this: &Interpreter,
|
|
||||||
name: &str,
|
|
||||||
id: u32,
|
|
||||||
bubbles: bool,
|
|
||||||
handler: &Function,
|
|
||||||
);
|
|
||||||
|
|
||||||
#[wasm_bindgen(method)]
|
|
||||||
pub fn RemoveEventListener(this: &Interpreter, name: &str, id: u32);
|
|
||||||
|
|
||||||
#[wasm_bindgen(method)]
|
|
||||||
pub fn RemoveAttribute(this: &Interpreter, id: u32, field: &str, ns: Option<&str>);
|
|
||||||
|
|
||||||
#[wasm_bindgen(method)]
|
|
||||||
pub fn Remove(this: &Interpreter, id: u32);
|
|
||||||
|
|
||||||
#[wasm_bindgen(method)]
|
|
||||||
pub fn PushRoot(this: &Interpreter, id: u32);
|
|
||||||
|
|
||||||
#[wasm_bindgen(method)]
|
|
||||||
pub fn AppendChildren(this: &Interpreter, id: u32, m: u32);
|
|
||||||
}
|
|
||||||
@@ -1,390 +1,11 @@
|
|||||||
import { setAttributeInner } from "./common.js";
|
import { setAttributeInner } from "./common.js";
|
||||||
|
|
||||||
class ListenerMap {
|
|
||||||
constructor(root) {
|
|
||||||
// bubbling events can listen at the root element
|
|
||||||
this.global = {};
|
|
||||||
// non bubbling events listen at the element the listener was created at
|
|
||||||
this.local = {};
|
|
||||||
this.root = root;
|
|
||||||
}
|
|
||||||
|
|
||||||
create(event_name, element, handler, bubbles) {
|
|
||||||
if (bubbles) {
|
|
||||||
if (this.global[event_name] === undefined) {
|
|
||||||
this.global[event_name] = {};
|
|
||||||
this.global[event_name].active = 1;
|
|
||||||
this.global[event_name].callback = handler;
|
|
||||||
this.root.addEventListener(event_name, handler);
|
|
||||||
} else {
|
|
||||||
this.global[event_name].active++;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const id = element.getAttribute("data-dioxus-id");
|
|
||||||
if (!this.local[id]) {
|
|
||||||
this.local[id] = {};
|
|
||||||
}
|
|
||||||
this.local[id][event_name] = handler;
|
|
||||||
element.addEventListener(event_name, handler);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
remove(element, event_name, bubbles) {
|
|
||||||
if (bubbles) {
|
|
||||||
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];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const id = element.getAttribute("data-dioxus-id");
|
|
||||||
delete this.local[id][event_name];
|
|
||||||
if (this.local[id].length === 0) {
|
|
||||||
delete this.local[id];
|
|
||||||
}
|
|
||||||
element.removeEventListener(event_name, handler);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
removeAllNonBubbling(element) {
|
|
||||||
const id = element.getAttribute("data-dioxus-id");
|
|
||||||
delete this.local[id];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class InterpreterConfig {
|
class InterpreterConfig {
|
||||||
constructor(intercept_link_redirects) {
|
constructor(intercept_link_redirects) {
|
||||||
this.intercept_link_redirects = intercept_link_redirects;
|
this.intercept_link_redirects = intercept_link_redirects;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Interpreter {
|
|
||||||
constructor(root, config) {
|
|
||||||
this.config = config;
|
|
||||||
this.root = root;
|
|
||||||
this.listeners = new ListenerMap(root);
|
|
||||||
this.nodes = [root];
|
|
||||||
this.stack = [root];
|
|
||||||
this.handlers = {};
|
|
||||||
this.templates = {};
|
|
||||||
this.lastNodeWasText = false;
|
|
||||||
}
|
|
||||||
top() {
|
|
||||||
return this.stack[this.stack.length - 1];
|
|
||||||
}
|
|
||||||
pop() {
|
|
||||||
return this.stack.pop();
|
|
||||||
}
|
|
||||||
MountToRoot() {
|
|
||||||
this.AppendChildren(this.stack.length - 1);
|
|
||||||
}
|
|
||||||
SetNode(id, node) {
|
|
||||||
this.nodes[id] = node;
|
|
||||||
}
|
|
||||||
PushRoot(root) {
|
|
||||||
const node = this.nodes[root];
|
|
||||||
this.stack.push(node);
|
|
||||||
}
|
|
||||||
PopRoot() {
|
|
||||||
this.stack.pop();
|
|
||||||
}
|
|
||||||
AppendChildren(many) {
|
|
||||||
// let root = this.nodes[id];
|
|
||||||
let root = this.stack[this.stack.length - 1 - many];
|
|
||||||
let to_add = this.stack.splice(this.stack.length - many);
|
|
||||||
for (let i = 0; i < many; i++) {
|
|
||||||
root.appendChild(to_add[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ReplaceWith(root_id, m) {
|
|
||||||
let root = this.nodes[root_id];
|
|
||||||
let els = this.stack.splice(this.stack.length - m);
|
|
||||||
if (is_element_node(root.nodeType)) {
|
|
||||||
this.listeners.removeAllNonBubbling(root);
|
|
||||||
}
|
|
||||||
root.replaceWith(...els);
|
|
||||||
}
|
|
||||||
InsertAfter(root, n) {
|
|
||||||
let old = this.nodes[root];
|
|
||||||
let new_nodes = this.stack.splice(this.stack.length - n);
|
|
||||||
old.after(...new_nodes);
|
|
||||||
}
|
|
||||||
InsertBefore(root, n) {
|
|
||||||
let old = this.nodes[root];
|
|
||||||
let new_nodes = this.stack.splice(this.stack.length - n);
|
|
||||||
old.before(...new_nodes);
|
|
||||||
}
|
|
||||||
Remove(root) {
|
|
||||||
let node = this.nodes[root];
|
|
||||||
if (node !== undefined) {
|
|
||||||
if (is_element_node(node)) {
|
|
||||||
this.listeners.removeAllNonBubbling(node);
|
|
||||||
}
|
|
||||||
node.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CreateTextNode(text, root) {
|
|
||||||
const node = document.createTextNode(text);
|
|
||||||
this.nodes[root] = node;
|
|
||||||
this.stack.push(node);
|
|
||||||
}
|
|
||||||
CreatePlaceholder(root) {
|
|
||||||
let el = document.createElement("pre");
|
|
||||||
el.hidden = true;
|
|
||||||
this.stack.push(el);
|
|
||||||
this.nodes[root] = el;
|
|
||||||
}
|
|
||||||
NewEventListener(event_name, root, bubbles, handler) {
|
|
||||||
const element = this.nodes[root];
|
|
||||||
element.setAttribute("data-dioxus-id", `${root}`);
|
|
||||||
this.listeners.create(event_name, element, handler, bubbles);
|
|
||||||
}
|
|
||||||
RemoveEventListener(root, event_name, bubbles) {
|
|
||||||
const element = this.nodes[root];
|
|
||||||
element.removeAttribute(`data-dioxus-id`);
|
|
||||||
this.listeners.remove(element, event_name, bubbles);
|
|
||||||
}
|
|
||||||
SetText(root, text) {
|
|
||||||
this.nodes[root].textContent = text;
|
|
||||||
}
|
|
||||||
SetAttribute(id, field, value, ns) {
|
|
||||||
if (value === null) {
|
|
||||||
this.RemoveAttribute(id, field, ns);
|
|
||||||
} else {
|
|
||||||
const node = this.nodes[id];
|
|
||||||
setAttributeInner(node, field, value, ns);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RemoveAttribute(root, field, ns) {
|
|
||||||
const node = this.nodes[root];
|
|
||||||
if (!ns) {
|
|
||||||
switch (field) {
|
|
||||||
case "value":
|
|
||||||
node.value = "";
|
|
||||||
break;
|
|
||||||
case "checked":
|
|
||||||
node.checked = false;
|
|
||||||
break;
|
|
||||||
case "selected":
|
|
||||||
node.selected = false;
|
|
||||||
break;
|
|
||||||
case "dangerous_inner_html":
|
|
||||||
node.innerHTML = "";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
node.removeAttribute(field);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else if (ns == "style") {
|
|
||||||
node.style.removeProperty(name);
|
|
||||||
} else {
|
|
||||||
node.removeAttributeNS(ns, field);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GetClientRect(id) {
|
|
||||||
const node = this.nodes[id];
|
|
||||||
if (!node) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const rect = node.getBoundingClientRect();
|
|
||||||
return {
|
|
||||||
type: "GetClientRect",
|
|
||||||
origin: [rect.x, rect.y],
|
|
||||||
size: [rect.width, rect.height],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
ScrollTo(id, behavior) {
|
|
||||||
const node = this.nodes[id];
|
|
||||||
if (!node) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
node.scrollIntoView({
|
|
||||||
behavior: behavior,
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the focus on the element
|
|
||||||
SetFocus(id, focus) {
|
|
||||||
const node = this.nodes[id];
|
|
||||||
if (!node) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (focus) {
|
|
||||||
node.focus();
|
|
||||||
} else {
|
|
||||||
node.blur();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleEdits(edits) {
|
|
||||||
for (let template of edits.templates) {
|
|
||||||
this.SaveTemplate(template);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let edit of edits.edits) {
|
|
||||||
this.handleEdit(edit);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*POST_HANDLE_EDITS*/
|
|
||||||
}
|
|
||||||
|
|
||||||
SaveTemplate(template) {
|
|
||||||
let roots = [];
|
|
||||||
for (let root of template.roots) {
|
|
||||||
roots.push(this.MakeTemplateNode(root));
|
|
||||||
}
|
|
||||||
this.templates[template.name] = roots;
|
|
||||||
}
|
|
||||||
|
|
||||||
MakeTemplateNode(node) {
|
|
||||||
switch (node.type) {
|
|
||||||
case "Text":
|
|
||||||
return document.createTextNode(node.text);
|
|
||||||
case "Dynamic":
|
|
||||||
let dyn = document.createElement("pre");
|
|
||||||
dyn.hidden = true;
|
|
||||||
return dyn;
|
|
||||||
case "DynamicText":
|
|
||||||
return document.createTextNode("placeholder");
|
|
||||||
case "Element":
|
|
||||||
let el;
|
|
||||||
|
|
||||||
if (node.namespace != null) {
|
|
||||||
el = document.createElementNS(node.namespace, node.tag);
|
|
||||||
} else {
|
|
||||||
el = document.createElement(node.tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let attr of node.attrs) {
|
|
||||||
if (attr.type == "Static") {
|
|
||||||
setAttributeInner(el, attr.name, attr.value, attr.namespace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let child of node.children) {
|
|
||||||
el.appendChild(this.MakeTemplateNode(child));
|
|
||||||
}
|
|
||||||
|
|
||||||
return el;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AssignId(path, id) {
|
|
||||||
this.nodes[id] = this.LoadChild(path);
|
|
||||||
}
|
|
||||||
LoadChild(path) {
|
|
||||||
// iterate through each number and get that child
|
|
||||||
let node = this.stack[this.stack.length - 1];
|
|
||||||
|
|
||||||
for (let i = 0; i < path.length; i++) {
|
|
||||||
node = node.childNodes[path[i]];
|
|
||||||
}
|
|
||||||
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
HydrateText(path, value, id) {
|
|
||||||
let node = this.LoadChild(path);
|
|
||||||
|
|
||||||
if (node.nodeType == Node.TEXT_NODE) {
|
|
||||||
node.textContent = value;
|
|
||||||
} else {
|
|
||||||
// replace with a textnode
|
|
||||||
let text = document.createTextNode(value);
|
|
||||||
node.replaceWith(text);
|
|
||||||
node = text;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.nodes[id] = node;
|
|
||||||
}
|
|
||||||
ReplacePlaceholder(path, m) {
|
|
||||||
let els = this.stack.splice(this.stack.length - m);
|
|
||||||
let node = this.LoadChild(path);
|
|
||||||
node.replaceWith(...els);
|
|
||||||
}
|
|
||||||
LoadTemplate(name, index, id) {
|
|
||||||
let node = this.templates[name][index].cloneNode(true);
|
|
||||||
this.nodes[id] = node;
|
|
||||||
this.stack.push(node);
|
|
||||||
}
|
|
||||||
handleEdit(edit) {
|
|
||||||
switch (edit.type) {
|
|
||||||
case "AppendChildren":
|
|
||||||
this.AppendChildren(edit.m);
|
|
||||||
break;
|
|
||||||
case "AssignId":
|
|
||||||
this.AssignId(edit.path, edit.id);
|
|
||||||
break;
|
|
||||||
case "CreatePlaceholder":
|
|
||||||
this.CreatePlaceholder(edit.id);
|
|
||||||
break;
|
|
||||||
case "CreateTextNode":
|
|
||||||
this.CreateTextNode(edit.value, edit.id);
|
|
||||||
break;
|
|
||||||
case "HydrateText":
|
|
||||||
this.HydrateText(edit.path, edit.value, edit.id);
|
|
||||||
break;
|
|
||||||
case "LoadTemplate":
|
|
||||||
this.LoadTemplate(edit.name, edit.index, edit.id);
|
|
||||||
break;
|
|
||||||
case "PushRoot":
|
|
||||||
this.PushRoot(edit.id);
|
|
||||||
break;
|
|
||||||
case "ReplaceWith":
|
|
||||||
this.ReplaceWith(edit.id, edit.m);
|
|
||||||
break;
|
|
||||||
case "ReplacePlaceholder":
|
|
||||||
this.ReplacePlaceholder(edit.path, edit.m);
|
|
||||||
break;
|
|
||||||
case "InsertAfter":
|
|
||||||
this.InsertAfter(edit.id, edit.m);
|
|
||||||
break;
|
|
||||||
case "InsertBefore":
|
|
||||||
this.InsertBefore(edit.id, edit.m);
|
|
||||||
break;
|
|
||||||
case "Remove":
|
|
||||||
this.Remove(edit.id);
|
|
||||||
break;
|
|
||||||
case "SetText":
|
|
||||||
this.SetText(edit.id, edit.value);
|
|
||||||
break;
|
|
||||||
case "SetAttribute":
|
|
||||||
this.SetAttribute(edit.id, edit.name, edit.value, edit.ns);
|
|
||||||
break;
|
|
||||||
case "RemoveAttribute":
|
|
||||||
this.RemoveAttribute(edit.id, edit.name, edit.ns);
|
|
||||||
break;
|
|
||||||
case "RemoveEventListener":
|
|
||||||
this.RemoveEventListener(edit.id, edit.name);
|
|
||||||
break;
|
|
||||||
case "NewEventListener":
|
|
||||||
let bubbles = event_bubbles(edit.name);
|
|
||||||
|
|
||||||
// if this is a mounted listener, we send the event immediately
|
|
||||||
if (edit.name === "mounted") {
|
|
||||||
window.ipc.postMessage(
|
|
||||||
serializeIpcMessage("user_event", {
|
|
||||||
name: edit.name,
|
|
||||||
element: edit.id,
|
|
||||||
data: null,
|
|
||||||
bubbles,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
this.NewEventListener(edit.name, edit.id, bubbles, (event) => {
|
|
||||||
handler(event, edit.name, bubbles, this.config);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// this handler is only provided on the desktop and liveview implementations since this
|
// this handler is only provided on the desktop and liveview implementations since this
|
||||||
// method is not used by the web implementation
|
// method is not used by the web implementation
|
||||||
function handler(event, name, bubbles, config) {
|
function handler(event, name, bubbles, config) {
|
||||||
@@ -444,7 +65,44 @@ function handler(event, name, bubbles, config) {
|
|||||||
|
|
||||||
let contents = serialize_event(event);
|
let contents = serialize_event(event);
|
||||||
|
|
||||||
/*POST_EVENT_SERIALIZATION*/
|
// TODO: this should be liveview only
|
||||||
|
if (
|
||||||
|
target.tagName === "INPUT" &&
|
||||||
|
(event.type === "change" || event.type === "input")
|
||||||
|
) {
|
||||||
|
const type = target.getAttribute("type");
|
||||||
|
if (type === "file") {
|
||||||
|
async function read_files() {
|
||||||
|
const files = target.files;
|
||||||
|
const file_contents = {};
|
||||||
|
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
const file = files[i];
|
||||||
|
|
||||||
|
file_contents[file.name] = Array.from(
|
||||||
|
new Uint8Array(await file.arrayBuffer())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let file_engine = {
|
||||||
|
files: file_contents,
|
||||||
|
};
|
||||||
|
contents.files = file_engine;
|
||||||
|
|
||||||
|
if (realId === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const message = serializeIpcMessage("user_event", {
|
||||||
|
name: name,
|
||||||
|
element: parseInt(realId),
|
||||||
|
data: contents,
|
||||||
|
bubbles,
|
||||||
|
});
|
||||||
|
window.ipc.postMessage(message);
|
||||||
|
}
|
||||||
|
read_files();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
target.tagName === "FORM" &&
|
target.tagName === "FORM" &&
|
||||||
@@ -506,6 +164,239 @@ function find_real_id(target) {
|
|||||||
return realId;
|
return realId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ListenerMap {
|
||||||
|
constructor(root) {
|
||||||
|
// bubbling events can listen at the root element
|
||||||
|
this.global = {};
|
||||||
|
// non bubbling events listen at the element the listener was created at
|
||||||
|
this.local = {};
|
||||||
|
this.root = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
create(event_name, element, bubbles, handler) {
|
||||||
|
if (bubbles) {
|
||||||
|
if (this.global[event_name] === undefined) {
|
||||||
|
this.global[event_name] = {};
|
||||||
|
this.global[event_name].active = 1;
|
||||||
|
this.root.addEventListener(event_name, 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, handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(element, event_name, bubbles) {
|
||||||
|
if (bubbles) {
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const id = element.getAttribute("data-dioxus-id");
|
||||||
|
delete this.local[id][event_name];
|
||||||
|
if (this.local[id].length === 0) {
|
||||||
|
delete this.local[id];
|
||||||
|
}
|
||||||
|
element.removeEventListener(event_name, this.global[event_name].callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removeAllNonBubbling(element) {
|
||||||
|
const id = element.getAttribute("data-dioxus-id");
|
||||||
|
delete this.local[id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function SetAttributeInner(node, field, value, ns) {
|
||||||
|
const name = field;
|
||||||
|
if (ns === "style") {
|
||||||
|
// ????? why do we need to do this
|
||||||
|
if (node.style === undefined) {
|
||||||
|
node.style = {};
|
||||||
|
}
|
||||||
|
node.style[name] = value;
|
||||||
|
} else if (ns !== null && ns !== undefined && ns !== "") {
|
||||||
|
node.setAttributeNS(ns, name, value);
|
||||||
|
} else {
|
||||||
|
switch (name) {
|
||||||
|
case "value":
|
||||||
|
if (value !== node.value) {
|
||||||
|
node.value = value;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "initial_value":
|
||||||
|
node.defaultValue = value;
|
||||||
|
break;
|
||||||
|
case "checked":
|
||||||
|
node.checked = truthy(value);
|
||||||
|
break;
|
||||||
|
case "selected":
|
||||||
|
node.selected = truthy(value);
|
||||||
|
break;
|
||||||
|
case "dangerous_inner_html":
|
||||||
|
node.innerHTML = value;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364
|
||||||
|
if (!truthy(value) && bool_attrs.hasOwnProperty(name)) {
|
||||||
|
node.removeAttribute(name);
|
||||||
|
} else {
|
||||||
|
node.setAttribute(name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function LoadChild(array) {
|
||||||
|
// iterate through each number and get that child
|
||||||
|
node = stack[stack.length - 1];
|
||||||
|
|
||||||
|
for (let i = 0; i < array.length; i++) {
|
||||||
|
end = array[i];
|
||||||
|
for (node = node.firstChild; end > 0; end--) {
|
||||||
|
node = node.nextSibling;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
const listeners = new ListenerMap();
|
||||||
|
let nodes = [];
|
||||||
|
let stack = [];
|
||||||
|
let root;
|
||||||
|
const templates = {};
|
||||||
|
let node, els, end, k;
|
||||||
|
function initialize(root) {
|
||||||
|
nodes = [root];
|
||||||
|
stack = [root];
|
||||||
|
listeners.root = root;
|
||||||
|
}
|
||||||
|
function AppendChildren(id, many) {
|
||||||
|
root = nodes[id];
|
||||||
|
els = stack.splice(stack.length - many);
|
||||||
|
for (k = 0; k < many; k++) {
|
||||||
|
root.appendChild(els[k]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const bool_attrs = {
|
||||||
|
allowfullscreen: true,
|
||||||
|
allowpaymentrequest: true,
|
||||||
|
async: true,
|
||||||
|
autofocus: true,
|
||||||
|
autoplay: true,
|
||||||
|
checked: true,
|
||||||
|
controls: true,
|
||||||
|
default: true,
|
||||||
|
defer: true,
|
||||||
|
disabled: true,
|
||||||
|
formnovalidate: true,
|
||||||
|
hidden: true,
|
||||||
|
ismap: true,
|
||||||
|
itemscope: true,
|
||||||
|
loop: true,
|
||||||
|
multiple: true,
|
||||||
|
muted: true,
|
||||||
|
nomodule: true,
|
||||||
|
novalidate: true,
|
||||||
|
open: true,
|
||||||
|
playsinline: true,
|
||||||
|
readonly: true,
|
||||||
|
required: true,
|
||||||
|
reversed: true,
|
||||||
|
selected: true,
|
||||||
|
truespeed: true,
|
||||||
|
webkitdirectory: true,
|
||||||
|
};
|
||||||
|
function truthy(val) {
|
||||||
|
return val === "true" || val === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getClientRect(id) {
|
||||||
|
const node = nodes[id];
|
||||||
|
if (!node) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const rect = node.getBoundingClientRect();
|
||||||
|
return {
|
||||||
|
type: "GetClientRect",
|
||||||
|
origin: [rect.x, rect.y],
|
||||||
|
size: [rect.width, rect.height],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollTo(id, behavior) {
|
||||||
|
const node = nodes[id];
|
||||||
|
if (!node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
node.scrollIntoView({
|
||||||
|
behavior: behavior,
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the focus on the element
|
||||||
|
function setFocus(id, focus) {
|
||||||
|
const node = nodes[id];
|
||||||
|
if (!node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (focus) {
|
||||||
|
node.focus();
|
||||||
|
} else {
|
||||||
|
node.blur();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveTemplate(template) {
|
||||||
|
let roots = [];
|
||||||
|
for (let root of template.roots) {
|
||||||
|
roots.push(this.MakeTemplateNode(root));
|
||||||
|
}
|
||||||
|
this.templates[template.name] = roots;
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeTemplateNode(node) {
|
||||||
|
switch (node.type) {
|
||||||
|
case "Text":
|
||||||
|
return document.createTextNode(node.text);
|
||||||
|
case "Dynamic":
|
||||||
|
let dyn = document.createElement("pre");
|
||||||
|
dyn.hidden = true;
|
||||||
|
return dyn;
|
||||||
|
case "DynamicText":
|
||||||
|
return document.createTextNode("placeholder");
|
||||||
|
case "Element":
|
||||||
|
let el;
|
||||||
|
|
||||||
|
if (node.namespace != null) {
|
||||||
|
el = document.createElementNS(node.namespace, node.tag);
|
||||||
|
} else {
|
||||||
|
el = document.createElement(node.tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let attr of node.attrs) {
|
||||||
|
if (attr.type == "Static") {
|
||||||
|
setAttributeInner(el, attr.name, attr.value, attr.namespace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let child of node.children) {
|
||||||
|
el.appendChild(this.MakeTemplateNode(child));
|
||||||
|
}
|
||||||
|
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function get_mouse_data(event) {
|
function get_mouse_data(event) {
|
||||||
const {
|
const {
|
||||||
altKey,
|
altKey,
|
||||||
|
|||||||
@@ -6,14 +6,8 @@ mod sledgehammer_bindings;
|
|||||||
#[cfg(feature = "sledgehammer")]
|
#[cfg(feature = "sledgehammer")]
|
||||||
pub use sledgehammer_bindings::*;
|
pub use sledgehammer_bindings::*;
|
||||||
|
|
||||||
#[cfg(feature = "web")]
|
|
||||||
mod bindings;
|
|
||||||
|
|
||||||
#[cfg(feature = "web")]
|
|
||||||
pub use bindings::Interpreter;
|
|
||||||
|
|
||||||
// Common bindings for minimal usage.
|
// Common bindings for minimal usage.
|
||||||
#[cfg(feature = "minimal_bindings")]
|
#[cfg(all(feature = "minimal_bindings", feature = "web"))]
|
||||||
pub mod minimal_bindings {
|
pub mod minimal_bindings {
|
||||||
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
|
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
|
||||||
#[wasm_bindgen(module = "/src/common.js")]
|
#[wasm_bindgen(module = "/src/common.js")]
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
|
#[cfg(feature = "web")]
|
||||||
use js_sys::Function;
|
use js_sys::Function;
|
||||||
use sledgehammer_bindgen::bindgen;
|
use sledgehammer_bindgen::bindgen;
|
||||||
|
#[cfg(feature = "web")]
|
||||||
use web_sys::Node;
|
use web_sys::Node;
|
||||||
|
|
||||||
#[bindgen]
|
pub const SLEDGEHAMMER_JS: &str = GENERATED_JS;
|
||||||
|
#[cfg(feature = "web")]
|
||||||
|
#[bindgen(module)]
|
||||||
mod js {
|
mod js {
|
||||||
const JS: &str = r#"
|
const JS: &str = r#"
|
||||||
class ListenerMap {
|
class ListenerMap {
|
||||||
@@ -123,7 +127,7 @@ mod js {
|
|||||||
export function get_node(id) {
|
export function get_node(id) {
|
||||||
return nodes[id];
|
return nodes[id];
|
||||||
}
|
}
|
||||||
export function initilize(root, handler) {
|
export function initialize(root, handler) {
|
||||||
listeners.handler = handler;
|
listeners.handler = handler;
|
||||||
nodes = [root];
|
nodes = [root];
|
||||||
stack = [root];
|
stack = [root];
|
||||||
@@ -172,7 +176,7 @@ mod js {
|
|||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn save_template(nodes: Vec<Node>, tmpl_id: u32);
|
pub fn save_template(nodes: Vec<Node>, tmpl_id: u16);
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn set_node(id: u32, node: Node);
|
pub fn set_node(id: u32, node: Node);
|
||||||
@@ -181,7 +185,7 @@ mod js {
|
|||||||
pub fn get_node(id: u32) -> Node;
|
pub fn get_node(id: u32) -> Node;
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn initilize(root: Node, handler: &Function);
|
pub fn initialize(root: Node, handler: &Function);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mount_to_root() {
|
fn mount_to_root() {
|
||||||
@@ -190,19 +194,19 @@ mod js {
|
|||||||
fn push_root(root: u32) {
|
fn push_root(root: u32) {
|
||||||
"{stack.push(nodes[$root$]);}"
|
"{stack.push(nodes[$root$]);}"
|
||||||
}
|
}
|
||||||
fn append_children(id: u32, many: u32) {
|
fn append_children(id: u32, many: u16) {
|
||||||
"{AppendChildren($id$, $many$);}"
|
"{AppendChildren($id$, $many$);}"
|
||||||
}
|
}
|
||||||
fn pop_root() {
|
fn pop_root() {
|
||||||
"{stack.pop();}"
|
"{stack.pop();}"
|
||||||
}
|
}
|
||||||
fn replace_with(id: u32, n: u32) {
|
fn replace_with(id: u32, n: u16) {
|
||||||
"{root = nodes[$id$]; els = stack.splice(stack.length-$n$); if (root.listening) { listeners.removeAllNonBubbling(root); } root.replaceWith(...els);}"
|
"{root = nodes[$id$]; els = stack.splice(stack.length-$n$); if (root.listening) { listeners.removeAllNonBubbling(root); } root.replaceWith(...els);}"
|
||||||
}
|
}
|
||||||
fn insert_after(id: u32, n: u32) {
|
fn insert_after(id: u32, n: u16) {
|
||||||
"{nodes[$id$].after(...stack.splice(stack.length-$n$));}"
|
"{nodes[$id$].after(...stack.splice(stack.length-$n$));}"
|
||||||
}
|
}
|
||||||
fn insert_before(id: u32, n: u32) {
|
fn insert_before(id: u32, n: u16) {
|
||||||
"{nodes[$id$].before(...stack.splice(stack.length-$n$));}"
|
"{nodes[$id$].before(...stack.splice(stack.length-$n$));}"
|
||||||
}
|
}
|
||||||
fn remove(id: u32) {
|
fn remove(id: u32) {
|
||||||
@@ -273,10 +277,159 @@ mod js {
|
|||||||
nodes[$id$] = node;
|
nodes[$id$] = node;
|
||||||
}"#
|
}"#
|
||||||
}
|
}
|
||||||
fn replace_placeholder(ptr: u32, len: u8, n: u32) {
|
fn replace_placeholder(ptr: u32, len: u8, n: u16) {
|
||||||
"{els = stack.splice(stack.length - $n$); node = LoadChild($ptr$, $len$); node.replaceWith(...els);}"
|
"{els = stack.splice(stack.length - $n$); node = LoadChild($ptr$, $len$); node.replaceWith(...els);}"
|
||||||
}
|
}
|
||||||
fn load_template(tmpl_id: u32, index: u32, id: u32) {
|
fn load_template(tmpl_id: u16, index: u16, id: u32) {
|
||||||
"{node = templates[$tmpl_id$][$index$].cloneNode(true); nodes[$id$] = node; stack.push(node);}"
|
"{node = templates[$tmpl_id$][$index$].cloneNode(true); nodes[$id$] = node; stack.push(node);}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "web"))]
|
||||||
|
#[bindgen]
|
||||||
|
mod js {
|
||||||
|
const JS_FILE: &str = "./src/interpreter.js";
|
||||||
|
|
||||||
|
fn mount_to_root() {
|
||||||
|
"{AppendChildren(root, stack.length-1);}"
|
||||||
|
}
|
||||||
|
fn push_root(root: u32) {
|
||||||
|
"{stack.push(nodes[$root$]);}"
|
||||||
|
}
|
||||||
|
fn append_children(id: u32, many: u16) {
|
||||||
|
"{AppendChildren($id$, $many$);}"
|
||||||
|
}
|
||||||
|
fn append_children_to_top(many: u16) {
|
||||||
|
"{
|
||||||
|
root = stack[stack.length-many-1];
|
||||||
|
els = stack.splice(stack.length-many);
|
||||||
|
for (k = 0; k < many; k++) {
|
||||||
|
root.appendChild(els[k]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
}
|
||||||
|
fn pop_root() {
|
||||||
|
"{stack.pop();}"
|
||||||
|
}
|
||||||
|
fn replace_with(id: u32, n: u16) {
|
||||||
|
"{root = nodes[$id$]; els = stack.splice(stack.length-$n$); if (root.listening) { listeners.removeAllNonBubbling(root); } root.replaceWith(...els);}"
|
||||||
|
}
|
||||||
|
fn insert_after(id: u32, n: u16) {
|
||||||
|
"{nodes[$id$].after(...stack.splice(stack.length-$n$));}"
|
||||||
|
}
|
||||||
|
fn insert_before(id: u32, n: u16) {
|
||||||
|
"{nodes[$id$].before(...stack.splice(stack.length-$n$));}"
|
||||||
|
}
|
||||||
|
fn remove(id: u32) {
|
||||||
|
"{node = nodes[$id$]; if (node !== undefined) { if (node.listening) { listeners.removeAllNonBubbling(node); } node.remove(); }}"
|
||||||
|
}
|
||||||
|
fn create_raw_text(text: &str) {
|
||||||
|
"{stack.push(document.createTextNode($text$));}"
|
||||||
|
}
|
||||||
|
fn create_text_node(text: &str, id: u32) {
|
||||||
|
"{node = document.createTextNode($text$); nodes[$id$] = node; stack.push(node);}"
|
||||||
|
}
|
||||||
|
fn create_element(element: &str<u8, el>) {
|
||||||
|
"{stack.push(document.createElement($element$))}"
|
||||||
|
}
|
||||||
|
fn create_element_ns(element: &str<u8, el>, ns: &str<u8, namespace>) {
|
||||||
|
"{stack.push(document.createElementNS($ns$, $element$))}"
|
||||||
|
}
|
||||||
|
fn create_placeholder(id: u32) {
|
||||||
|
"{node = document.createElement('pre'); node.hidden = true; stack.push(node); nodes[$id$] = node;}"
|
||||||
|
}
|
||||||
|
fn add_placeholder() {
|
||||||
|
"{node = document.createElement('pre'); node.hidden = true; stack.push(node);}"
|
||||||
|
}
|
||||||
|
fn new_event_listener(event_name: &str<u8, evt>, id: u32, bubbles: u8) {
|
||||||
|
r#"
|
||||||
|
node = nodes[id];
|
||||||
|
if(node.listening){
|
||||||
|
node.listening += 1;
|
||||||
|
} else {
|
||||||
|
node.listening = 1;
|
||||||
|
}
|
||||||
|
node.setAttribute('data-dioxus-id', `\${id}`);
|
||||||
|
|
||||||
|
// if this is a mounted listener, we send the event immediately
|
||||||
|
if (edit.name === "mounted") {
|
||||||
|
window.ipc.postMessage(
|
||||||
|
serializeIpcMessage("user_event", {
|
||||||
|
name: edit.name,
|
||||||
|
element: edit.id,
|
||||||
|
data: null,
|
||||||
|
bubbles,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
listeners.create(event_name, node, bubbles, (event) => {
|
||||||
|
handler(event, event_name, bubbles, config);
|
||||||
|
});
|
||||||
|
}"#
|
||||||
|
}
|
||||||
|
fn remove_event_listener(event_name: &str<u8, evt>, id: u32, bubbles: u8) {
|
||||||
|
"{node = nodes[$id$]; node.listening -= 1; node.removeAttribute('data-dioxus-id'); listeners.remove(node, $event_name$, $bubbles$);}"
|
||||||
|
}
|
||||||
|
fn set_text(id: u32, text: &str) {
|
||||||
|
"{nodes[$id$].textContent = $text$;}"
|
||||||
|
}
|
||||||
|
fn set_attribute(id: u32, field: &str<u8, attr>, value: &str, ns: &str<u8, ns_cache>) {
|
||||||
|
"{node = nodes[$id$]; SetAttributeInner(node, $field$, $value$, $ns$);}"
|
||||||
|
}
|
||||||
|
fn set_top_attribute(field: &str<u8, attr>, value: &str, ns: &str<u8, ns_cache>) {
|
||||||
|
"{SetAttributeInner(stack[stack.length-1], $field$, $value$, $ns$);}"
|
||||||
|
}
|
||||||
|
fn remove_attribute(id: u32, field: &str<u8, attr>, ns: &str<u8, ns_cache>) {
|
||||||
|
r#"{
|
||||||
|
node = nodes[$id$];
|
||||||
|
if (!ns) {
|
||||||
|
switch (field) {
|
||||||
|
case "value":
|
||||||
|
node.value = "";
|
||||||
|
break;
|
||||||
|
case "checked":
|
||||||
|
node.checked = false;
|
||||||
|
break;
|
||||||
|
case "selected":
|
||||||
|
node.selected = false;
|
||||||
|
break;
|
||||||
|
case "dangerous_inner_html":
|
||||||
|
node.innerHTML = "";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
node.removeAttribute(field);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (ns == "style") {
|
||||||
|
node.style.removeProperty(name);
|
||||||
|
} else {
|
||||||
|
node.removeAttributeNS(ns, field);
|
||||||
|
}
|
||||||
|
}"#
|
||||||
|
}
|
||||||
|
fn assign_id(array: &[u8], id: u32) {
|
||||||
|
"{nodes[$id$] = LoadChild($array$);}"
|
||||||
|
}
|
||||||
|
fn hydrate_text(array: &[u8], value: &str, id: u32) {
|
||||||
|
r#"{
|
||||||
|
node = LoadChild($array$);
|
||||||
|
if (node.nodeType == Node.TEXT_NODE) {
|
||||||
|
node.textContent = value;
|
||||||
|
} else {
|
||||||
|
let text = document.createTextNode(value);
|
||||||
|
node.replaceWith(text);
|
||||||
|
node = text;
|
||||||
|
}
|
||||||
|
nodes[$id$] = node;
|
||||||
|
}"#
|
||||||
|
}
|
||||||
|
fn replace_placeholder(array: &[u8], n: u16) {
|
||||||
|
"{els = stack.splice(stack.length - $n$); node = LoadChild($array$); node.replaceWith(...els);}"
|
||||||
|
}
|
||||||
|
fn load_template(tmpl_id: u16, index: u16, id: u32) {
|
||||||
|
"{node = templates[$tmpl_id$][$index$].cloneNode(true); nodes[$id$] = node; stack.push(node);}"
|
||||||
|
}
|
||||||
|
fn add_templates(tmpl_id: u16, len: u16) {
|
||||||
|
"{templates[$tmpl_id$] = stack.splice(stack.length-$len$);}"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/src/minified.js
|
||||||
@@ -22,9 +22,10 @@ tokio-stream = { version = "0.1.11", features = ["net"] }
|
|||||||
tokio-util = { version = "0.7.4", features = ["rt"] }
|
tokio-util = { version = "0.7.4", features = ["rt"] }
|
||||||
serde = { version = "1.0.151", features = ["derive"] }
|
serde = { version = "1.0.151", features = ["derive"] }
|
||||||
serde_json = "1.0.91"
|
serde_json = "1.0.91"
|
||||||
|
rustc-hash = { workspace = true }
|
||||||
dioxus-html = { workspace = true, features = ["serialize"] }
|
dioxus-html = { workspace = true, features = ["serialize"] }
|
||||||
dioxus-core = { workspace = true, features = ["serialize"] }
|
dioxus-core = { workspace = true, features = ["serialize"] }
|
||||||
dioxus-interpreter-js = { workspace = true }
|
dioxus-interpreter-js = { workspace = true, features = ["sledgehammer"] }
|
||||||
dioxus-hot-reload = { workspace = true, optional = true }
|
dioxus-hot-reload = { workspace = true, optional = true }
|
||||||
|
|
||||||
# warp
|
# warp
|
||||||
@@ -52,6 +53,10 @@ axum = { version = "0.6.1", features = ["ws"] }
|
|||||||
salvo = { version = "0.44.1", features = ["affix", "ws"] }
|
salvo = { version = "0.44.1", features = ["affix", "ws"] }
|
||||||
tower = "0.4.13"
|
tower = "0.4.13"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
dioxus-interpreter-js = { workspace = true, features = ["sledgehammer"] }
|
||||||
|
minify-js = "0.5.6"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["hot-reload"]
|
default = ["hot-reload"]
|
||||||
# actix = ["actix-files", "actix-web", "actix-ws"]
|
# actix = ["actix-files", "actix-web", "actix-ws"]
|
||||||
@@ -61,6 +66,10 @@ hot-reload = ["dioxus-hot-reload"]
|
|||||||
name = "axum"
|
name = "axum"
|
||||||
required-features = ["axum"]
|
required-features = ["axum"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "axum_stress"
|
||||||
|
required-features = ["axum"]
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "salvo"
|
name = "salvo"
|
||||||
required-features = ["salvo"]
|
required-features = ["salvo"]
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
use dioxus_interpreter_js::COMMON_JS;
|
||||||
|
use dioxus_interpreter_js::SLEDGEHAMMER_JS;
|
||||||
|
use minify_js::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let serialize_file_uploads = r#"if (
|
||||||
|
target.tagName === "INPUT" &&
|
||||||
|
(event.type === "change" || event.type === "input")
|
||||||
|
) {
|
||||||
|
const type = target.getAttribute("type");
|
||||||
|
if (type === "file") {
|
||||||
|
async function read_files() {
|
||||||
|
const files = target.files;
|
||||||
|
const file_contents = {};
|
||||||
|
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
const file = files[i];
|
||||||
|
|
||||||
|
file_contents[file.name] = Array.from(
|
||||||
|
new Uint8Array(await file.arrayBuffer())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let file_engine = {
|
||||||
|
files: file_contents,
|
||||||
|
};
|
||||||
|
contents.files = file_engine;
|
||||||
|
|
||||||
|
if (realId === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const message = serializeIpcMessage("user_event", {
|
||||||
|
name: name,
|
||||||
|
element: parseInt(realId),
|
||||||
|
data: contents,
|
||||||
|
bubbles,
|
||||||
|
});
|
||||||
|
window.ipc.postMessage(message);
|
||||||
|
}
|
||||||
|
read_files();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
let mut interpreter =
|
||||||
|
SLEDGEHAMMER_JS.replace("/*POST_EVENT_SERIALIZATION*/", serialize_file_uploads);
|
||||||
|
while let Some(import_start) = interpreter.find("import") {
|
||||||
|
let import_end = interpreter[import_start..]
|
||||||
|
.find(|c| c == ';' || c == '\n')
|
||||||
|
.map(|i| i + import_start)
|
||||||
|
.unwrap_or_else(|| interpreter.len());
|
||||||
|
interpreter.replace_range(import_start..import_end, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
let main_js = std::fs::read_to_string("src/main.js").unwrap();
|
||||||
|
|
||||||
|
let js = format!("{interpreter}\n{main_js}");
|
||||||
|
// std::fs::write("src/minified.js", &js).unwrap();
|
||||||
|
|
||||||
|
let session = Session::new();
|
||||||
|
let mut out = Vec::new();
|
||||||
|
minify(&session, TopLevelMode::Module, js.as_bytes(), &mut out).unwrap();
|
||||||
|
let minified = String::from_utf8(out).unwrap();
|
||||||
|
std::fs::write("src/minified.js", minified).unwrap();
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
use axum::{extract::ws::WebSocketUpgrade, response::Html, routing::get, Router};
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
fn app(cx: Scope) -> Element {
|
||||||
|
let state = use_state(cx, || 0);
|
||||||
|
use_future(cx, (), |_| {
|
||||||
|
to_owned![state];
|
||||||
|
async move {
|
||||||
|
loop {
|
||||||
|
state += 1;
|
||||||
|
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.render(rsx! {
|
||||||
|
for _ in 0..10000 {
|
||||||
|
div {
|
||||||
|
"hello axum! {state}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
pretty_env_logger::init();
|
||||||
|
|
||||||
|
let addr: std::net::SocketAddr = ([127, 0, 0, 1], 3030).into();
|
||||||
|
|
||||||
|
let view = dioxus_liveview::LiveViewPool::new();
|
||||||
|
|
||||||
|
let app = Router::new()
|
||||||
|
.route(
|
||||||
|
"/",
|
||||||
|
get(move || async move {
|
||||||
|
Html(format!(
|
||||||
|
r#"
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head> <title>Dioxus LiveView with axum</title> </head>
|
||||||
|
<body> <div id="main"></div> </body>
|
||||||
|
{glue}
|
||||||
|
</html>
|
||||||
|
"#,
|
||||||
|
glue = dioxus_liveview::interpreter_glue(&format!("ws://{addr}/ws"))
|
||||||
|
))
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/ws",
|
||||||
|
get(move |ws: WebSocketUpgrade| async move {
|
||||||
|
ws.on_upgrade(move |socket| async move {
|
||||||
|
_ = view.launch(dioxus_liveview::axum_socket(socket), app).await;
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
println!("Listening on http://{addr}");
|
||||||
|
|
||||||
|
axum::Server::bind(&addr.to_string().parse().unwrap())
|
||||||
|
.serve(app.into_make_service())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
@@ -20,5 +20,5 @@ fn transform_rx(message: Result<Message, axum::Error>) -> Result<Vec<u8>, LiveVi
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn transform_tx(message: Vec<u8>) -> Result<Message, axum::Error> {
|
async fn transform_tx(message: Vec<u8>) -> Result<Message, axum::Error> {
|
||||||
Ok(Message::Text(String::from_utf8_lossy(&message).to_string()))
|
Ok(Message::Binary(message))
|
||||||
}
|
}
|
||||||
@@ -37,74 +37,18 @@ pub enum LiveViewError {
|
|||||||
SendingFailed,
|
SendingFailed,
|
||||||
}
|
}
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
static MINIFIED: &str = include_str!("./minified.js");
|
||||||
|
|
||||||
static INTERPRETER_JS: Lazy<String> = Lazy::new(|| {
|
|
||||||
let interpreter = dioxus_interpreter_js::INTERPRETER_JS;
|
|
||||||
let serialize_file_uploads = r#"if (
|
|
||||||
target.tagName === "INPUT" &&
|
|
||||||
(event.type === "change" || event.type === "input")
|
|
||||||
) {
|
|
||||||
const type = target.getAttribute("type");
|
|
||||||
if (type === "file") {
|
|
||||||
async function read_files() {
|
|
||||||
const files = target.files;
|
|
||||||
const file_contents = {};
|
|
||||||
|
|
||||||
for (let i = 0; i < files.length; i++) {
|
|
||||||
const file = files[i];
|
|
||||||
|
|
||||||
file_contents[file.name] = Array.from(
|
|
||||||
new Uint8Array(await file.arrayBuffer())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let file_engine = {
|
|
||||||
files: file_contents,
|
|
||||||
};
|
|
||||||
contents.files = file_engine;
|
|
||||||
|
|
||||||
if (realId === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const message = serializeIpcMessage("user_event", {
|
|
||||||
name: name,
|
|
||||||
element: parseInt(realId),
|
|
||||||
data: contents,
|
|
||||||
bubbles,
|
|
||||||
});
|
|
||||||
window.ipc.postMessage(message);
|
|
||||||
}
|
|
||||||
read_files();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}"#;
|
|
||||||
|
|
||||||
let interpreter = interpreter.replace("/*POST_EVENT_SERIALIZATION*/", serialize_file_uploads);
|
|
||||||
interpreter.replace("import { setAttributeInner } from \"./common.js\";", "")
|
|
||||||
});
|
|
||||||
|
|
||||||
static COMMON_JS: Lazy<String> = Lazy::new(|| {
|
|
||||||
let common = dioxus_interpreter_js::COMMON_JS;
|
|
||||||
common.replace("export", "")
|
|
||||||
});
|
|
||||||
|
|
||||||
static MAIN_JS: &str = include_str!("./main.js");
|
|
||||||
|
|
||||||
/// This script that gets injected into your app connects this page to the websocket endpoint
|
/// This script that gets injected into your app connects this page to the websocket endpoint
|
||||||
///
|
///
|
||||||
/// Once the endpoint is connected, it will send the initial state of the app, and then start
|
/// Once the endpoint is connected, it will send the initial state of the app, and then start
|
||||||
/// processing user events and returning edits to the liveview instance
|
/// processing user events and returning edits to the liveview instance
|
||||||
pub fn interpreter_glue(url: &str) -> String {
|
pub fn interpreter_glue(url: &str) -> String {
|
||||||
let js = &*INTERPRETER_JS;
|
|
||||||
let common = &*COMMON_JS;
|
|
||||||
format!(
|
format!(
|
||||||
r#"
|
r#"
|
||||||
<script>
|
<script>
|
||||||
var WS_ADDR = "{url}";
|
var WS_ADDR = "{url}";
|
||||||
{js}
|
{MINIFIED}
|
||||||
{common}
|
|
||||||
{MAIN_JS}
|
|
||||||
main();
|
|
||||||
</script>
|
</script>
|
||||||
"#
|
"#
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,10 +7,9 @@ function main() {
|
|||||||
|
|
||||||
class IPC {
|
class IPC {
|
||||||
constructor(root) {
|
constructor(root) {
|
||||||
// connect to the websocket
|
initialize(root);
|
||||||
window.interpreter = new Interpreter(root, new InterpreterConfig(false));
|
const ws = new WebSocket(WS_ADDR);
|
||||||
|
ws.binaryType = "arraybuffer";
|
||||||
let ws = new WebSocket(WS_ADDR);
|
|
||||||
|
|
||||||
function ping() {
|
function ping() {
|
||||||
ws.send("__ping__");
|
ws.send("__ping__");
|
||||||
@@ -27,17 +26,23 @@ class IPC {
|
|||||||
};
|
};
|
||||||
|
|
||||||
ws.onmessage = (message) => {
|
ws.onmessage = (message) => {
|
||||||
// Ignore pongs
|
if (message.data instanceof ArrayBuffer) {
|
||||||
if (message.data != "__pong__") {
|
// binary frame
|
||||||
const event = JSON.parse(message.data);
|
run_from_bytes(message.data);
|
||||||
switch (event.type) {
|
} else {
|
||||||
case "edits":
|
// text frame
|
||||||
let edits = event.data;
|
// Ignore pongs
|
||||||
window.interpreter.handleEdits(edits);
|
if (message.data != "__pong__") {
|
||||||
break;
|
const event = JSON.parse(message.data);
|
||||||
case "query":
|
switch (event.type) {
|
||||||
Function("Eval", `"use strict";${event.data};`)();
|
case "edits":
|
||||||
break;
|
let edits = event.data;
|
||||||
|
window.interpreter.handleEdits(edits);
|
||||||
|
break;
|
||||||
|
case "query":
|
||||||
|
Function("Eval", `"use strict";${event.data};`)();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -49,3 +54,5 @@ class IPC {
|
|||||||
this.ws.send(msg);
|
this.ws.send(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
+159
-16
@@ -4,9 +4,11 @@ use crate::{
|
|||||||
query::{QueryEngine, QueryResult},
|
query::{QueryEngine, QueryResult},
|
||||||
LiveViewError,
|
LiveViewError,
|
||||||
};
|
};
|
||||||
use dioxus_core::{prelude::*, Mutations};
|
use dioxus_core::{prelude::*, BorrowedAttributeValue, Mutations};
|
||||||
use dioxus_html::{EventData, HtmlEvent, MountedData};
|
use dioxus_html::{event_bubbles, EventData, HtmlEvent, MountedData};
|
||||||
|
use dioxus_interpreter_js::Channel;
|
||||||
use futures_util::{pin_mut, SinkExt, StreamExt};
|
use futures_util::{pin_mut, SinkExt, StreamExt};
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::{rc::Rc, time::Duration};
|
use std::{rc::Rc, time::Duration};
|
||||||
use tokio_util::task::LocalPoolHandle;
|
use tokio_util::task::LocalPoolHandle;
|
||||||
@@ -107,7 +109,7 @@ impl<S> LiveViewSocket for S where
|
|||||||
///
|
///
|
||||||
/// This function makes it easy to integrate Dioxus LiveView with any socket-based framework.
|
/// This function makes it easy to integrate Dioxus LiveView with any socket-based framework.
|
||||||
///
|
///
|
||||||
/// As long as your framework can provide a Sink and Stream of Strings, you can use this function.
|
/// As long as your framework can provide a Sink and Stream of Bytes, you can use this function.
|
||||||
///
|
///
|
||||||
/// You might need to transform the error types of the web backend into the LiveView error type.
|
/// You might need to transform the error types of the web backend into the LiveView error type.
|
||||||
pub async fn run(mut vdom: VirtualDom, ws: impl LiveViewSocket) -> Result<(), LiveViewError> {
|
pub async fn run(mut vdom: VirtualDom, ws: impl LiveViewSocket) -> Result<(), LiveViewError> {
|
||||||
@@ -120,20 +122,31 @@ pub async fn run(mut vdom: VirtualDom, ws: impl LiveViewSocket) -> Result<(), Li
|
|||||||
rx
|
rx
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut templates: FxHashMap<String, u16> = Default::default();
|
||||||
|
let mut max_template_count = 0;
|
||||||
|
|
||||||
// Create the a proxy for query engine
|
// Create the a proxy for query engine
|
||||||
let (query_tx, mut query_rx) = tokio::sync::mpsc::unbounded_channel();
|
let (query_tx, mut query_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||||
let query_engine = QueryEngine::new(query_tx);
|
let query_engine = QueryEngine::new(query_tx);
|
||||||
vdom.base_scope().provide_context(query_engine.clone());
|
vdom.base_scope().provide_context(query_engine.clone());
|
||||||
init_eval(vdom.base_scope());
|
init_eval(vdom.base_scope());
|
||||||
|
|
||||||
// todo: use an efficient binary packed format for this
|
|
||||||
let edits = serde_json::to_string(&ClientUpdate::Edits(vdom.rebuild())).unwrap();
|
|
||||||
|
|
||||||
// pin the futures so we can use select!
|
// pin the futures so we can use select!
|
||||||
pin_mut!(ws);
|
pin_mut!(ws);
|
||||||
|
|
||||||
// send the initial render to the client
|
let mut edit_channel = Channel::default();
|
||||||
ws.send(edits.into_bytes()).await?;
|
if let Some(edits) = {
|
||||||
|
let mutations = vdom.rebuild();
|
||||||
|
apply_edits(
|
||||||
|
mutations,
|
||||||
|
&mut edit_channel,
|
||||||
|
&mut templates,
|
||||||
|
&mut max_template_count,
|
||||||
|
)
|
||||||
|
} {
|
||||||
|
// send the initial render to the client
|
||||||
|
ws.send(edits).await?;
|
||||||
|
}
|
||||||
|
|
||||||
// desktop uses this wrapper struct thing around the actual event itself
|
// desktop uses this wrapper struct thing around the actual event itself
|
||||||
// this is sorta driven by tao/wry
|
// this is sorta driven by tao/wry
|
||||||
@@ -160,7 +173,7 @@ pub async fn run(mut vdom: VirtualDom, ws: impl LiveViewSocket) -> Result<(), Li
|
|||||||
match evt.as_ref().map(|o| o.as_deref()) {
|
match evt.as_ref().map(|o| o.as_deref()) {
|
||||||
// respond with a pong every ping to keep the websocket alive
|
// respond with a pong every ping to keep the websocket alive
|
||||||
Some(Ok(b"__ping__")) => {
|
Some(Ok(b"__ping__")) => {
|
||||||
ws.send(b"__pong__".to_vec()).await?;
|
// ws.send(b"__pong__".to_vec()).await?;
|
||||||
}
|
}
|
||||||
Some(Ok(evt)) => {
|
Some(Ok(evt)) => {
|
||||||
if let Ok(message) = serde_json::from_str::<IpcMessage>(&String::from_utf8_lossy(evt)) {
|
if let Ok(message) = serde_json::from_str::<IpcMessage>(&String::from_utf8_lossy(evt)) {
|
||||||
@@ -199,7 +212,7 @@ pub async fn run(mut vdom: VirtualDom, ws: impl LiveViewSocket) -> Result<(), Li
|
|||||||
|
|
||||||
// handle any new queries
|
// handle any new queries
|
||||||
Some(query) = query_rx.recv() => {
|
Some(query) = query_rx.recv() => {
|
||||||
ws.send(serde_json::to_string(&ClientUpdate::Query(query)).unwrap().into_bytes()).await?;
|
// ws.send(serde_json::to_string(&ClientUpdate::Query(query)).unwrap().into_bytes()).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(msg) = hot_reload_wait => {
|
Some(msg) = hot_reload_wait => {
|
||||||
@@ -221,15 +234,145 @@ pub async fn run(mut vdom: VirtualDom, ws: impl LiveViewSocket) -> Result<(), Li
|
|||||||
.render_with_deadline(tokio::time::sleep(Duration::from_millis(10)))
|
.render_with_deadline(tokio::time::sleep(Duration::from_millis(10)))
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
ws.send(
|
if let Some(edits) = {
|
||||||
serde_json::to_string(&ClientUpdate::Edits(edits))
|
apply_edits(
|
||||||
.unwrap()
|
edits,
|
||||||
.into_bytes(),
|
&mut edit_channel,
|
||||||
)
|
&mut templates,
|
||||||
.await?;
|
&mut max_template_count,
|
||||||
|
)
|
||||||
|
} {
|
||||||
|
ws.send(edits).await?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn add_template(
|
||||||
|
template: &Template,
|
||||||
|
channel: &mut Channel,
|
||||||
|
templates: &mut FxHashMap<String, u16>,
|
||||||
|
max_template_count: &mut u16,
|
||||||
|
) {
|
||||||
|
for (idx, root) in template.roots.iter().enumerate() {
|
||||||
|
create_template_node(channel, root);
|
||||||
|
templates.insert(template.name.to_owned(), *max_template_count);
|
||||||
|
}
|
||||||
|
channel.add_templates(*max_template_count, template.roots.len() as u16);
|
||||||
|
|
||||||
|
*max_template_count += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_template_node(channel: &mut Channel, v: &TemplateNode) {
|
||||||
|
use TemplateNode::*;
|
||||||
|
match v {
|
||||||
|
Element {
|
||||||
|
tag,
|
||||||
|
namespace,
|
||||||
|
attrs,
|
||||||
|
children,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
// Push the current node onto the stack
|
||||||
|
match namespace {
|
||||||
|
Some(ns) => channel.create_element_ns(tag, ns),
|
||||||
|
None => channel.create_element(tag),
|
||||||
|
}
|
||||||
|
// Set attributes on the current node
|
||||||
|
for attr in *attrs {
|
||||||
|
if let TemplateAttribute::Static {
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
namespace,
|
||||||
|
} = attr
|
||||||
|
{
|
||||||
|
channel.set_top_attribute(name, value, namespace.unwrap_or_default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add each child to the stack
|
||||||
|
for child in *children {
|
||||||
|
create_template_node(channel, child);
|
||||||
|
}
|
||||||
|
// Add all children to the parent
|
||||||
|
channel.append_children_to_top(children.len() as u16);
|
||||||
|
}
|
||||||
|
Text { text } => channel.create_raw_text(text),
|
||||||
|
DynamicText { .. } => channel.create_raw_text("p"),
|
||||||
|
Dynamic { .. } => channel.add_placeholder(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_edits(
|
||||||
|
mutations: Mutations,
|
||||||
|
channel: &mut Channel,
|
||||||
|
templates: &mut FxHashMap<String, u16>,
|
||||||
|
max_template_count: &mut u16,
|
||||||
|
) -> Option<Vec<u8>> {
|
||||||
|
use dioxus_core::Mutation::*;
|
||||||
|
if mutations.templates.is_empty() && mutations.edits.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
for template in mutations.templates {
|
||||||
|
add_template(&template, channel, templates, max_template_count);
|
||||||
|
}
|
||||||
|
for edit in mutations.edits {
|
||||||
|
match edit {
|
||||||
|
AppendChildren { id, m } => channel.append_children(id.0 as u32, m as u16),
|
||||||
|
AssignId { path, id } => channel.assign_id(path, id.0 as u32),
|
||||||
|
CreatePlaceholder { id } => channel.create_placeholder(id.0 as u32),
|
||||||
|
CreateTextNode { value, id } => channel.create_text_node(value, id.0 as u32),
|
||||||
|
HydrateText { path, value, id } => channel.hydrate_text(path, value, id.0 as u32),
|
||||||
|
LoadTemplate { name, index, id } => {
|
||||||
|
if let Some(tmpl_id) = templates.get(name) {
|
||||||
|
channel.load_template(*tmpl_id as u16, index as u16, id.0 as u32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ReplaceWith { id, m } => channel.replace_with(id.0 as u32, m as u16),
|
||||||
|
ReplacePlaceholder { path, m } => channel.replace_placeholder(path, m as u16),
|
||||||
|
InsertAfter { id, m } => channel.insert_after(id.0 as u32, m as u16),
|
||||||
|
InsertBefore { id, m } => channel.insert_before(id.0 as u32, m as u16),
|
||||||
|
SetAttribute {
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
id,
|
||||||
|
ns,
|
||||||
|
} => match value {
|
||||||
|
BorrowedAttributeValue::Text(txt) => {
|
||||||
|
channel.set_attribute(id.0 as u32, name, txt, ns.unwrap_or_default())
|
||||||
|
}
|
||||||
|
BorrowedAttributeValue::Float(f) => {
|
||||||
|
channel.set_attribute(id.0 as u32, name, &f.to_string(), ns.unwrap_or_default())
|
||||||
|
}
|
||||||
|
BorrowedAttributeValue::Int(n) => {
|
||||||
|
channel.set_attribute(id.0 as u32, name, &n.to_string(), ns.unwrap_or_default())
|
||||||
|
}
|
||||||
|
BorrowedAttributeValue::Bool(b) => channel.set_attribute(
|
||||||
|
id.0 as u32,
|
||||||
|
name,
|
||||||
|
if b { "true" } else { "false" },
|
||||||
|
ns.unwrap_or_default(),
|
||||||
|
),
|
||||||
|
BorrowedAttributeValue::None => {
|
||||||
|
channel.remove_attribute(id.0 as u32, name, ns.unwrap_or_default())
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
},
|
||||||
|
SetText { value, id } => channel.set_text(id.0 as u32, value),
|
||||||
|
NewEventListener { name, id, .. } => {
|
||||||
|
channel.new_event_listener(name, id.0 as u32, event_bubbles(name) as u8)
|
||||||
|
}
|
||||||
|
RemoveEventListener { name, id } => {
|
||||||
|
channel.remove_event_listener(name, id.0 as u32, event_bubbles(name) as u8)
|
||||||
|
}
|
||||||
|
Remove { id } => channel.remove(id.0 as u32),
|
||||||
|
PushRoot { id } => channel.push_root(id.0 as u32),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let bytes: Vec<_> = channel.export_memory().collect();
|
||||||
|
channel.reset();
|
||||||
|
Some(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
#[serde(tag = "type", content = "data")]
|
#[serde(tag = "type", content = "data")]
|
||||||
enum ClientUpdate<'a> {
|
enum ClientUpdate<'a> {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ dioxus-html = { workspace = true, features = ["wasm-bind"] }
|
|||||||
dioxus-interpreter-js = { workspace = true, features = [
|
dioxus-interpreter-js = { workspace = true, features = [
|
||||||
"sledgehammer",
|
"sledgehammer",
|
||||||
"minimal_bindings",
|
"minimal_bindings",
|
||||||
|
"web",
|
||||||
] }
|
] }
|
||||||
|
|
||||||
js-sys = "0.3.56"
|
js-sys = "0.3.56"
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ pub struct WebsysDom {
|
|||||||
document: Document,
|
document: Document,
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub(crate) root: Element,
|
pub(crate) root: Element,
|
||||||
templates: FxHashMap<String, u32>,
|
templates: FxHashMap<String, u16>,
|
||||||
max_template_id: u32,
|
max_template_id: u16,
|
||||||
pub(crate) interpreter: Channel,
|
pub(crate) interpreter: Channel,
|
||||||
event_channel: mpsc::UnboundedSender<UiEvent>,
|
event_channel: mpsc::UnboundedSender<UiEvent>,
|
||||||
}
|
}
|
||||||
@@ -90,7 +90,7 @@ impl WebsysDom {
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
dioxus_interpreter_js::initilize(
|
dioxus_interpreter_js::initialize(
|
||||||
root.clone().unchecked_into(),
|
root.clone().unchecked_into(),
|
||||||
handler.as_ref().unchecked_ref(),
|
handler.as_ref().unchecked_ref(),
|
||||||
);
|
);
|
||||||
@@ -175,7 +175,7 @@ impl WebsysDom {
|
|||||||
let mut to_mount = Vec::new();
|
let mut to_mount = Vec::new();
|
||||||
for edit in &edits {
|
for edit in &edits {
|
||||||
match edit {
|
match edit {
|
||||||
AppendChildren { id, m } => i.append_children(id.0 as u32, *m as u32),
|
AppendChildren { id, m } => i.append_children(id.0 as u32, *m as u16),
|
||||||
AssignId { path, id } => {
|
AssignId { path, id } => {
|
||||||
i.assign_id(path.as_ptr() as u32, path.len() as u8, id.0 as u32)
|
i.assign_id(path.as_ptr() as u32, path.len() as u8, id.0 as u32)
|
||||||
}
|
}
|
||||||
@@ -186,15 +186,15 @@ impl WebsysDom {
|
|||||||
}
|
}
|
||||||
LoadTemplate { name, index, id } => {
|
LoadTemplate { name, index, id } => {
|
||||||
if let Some(tmpl_id) = self.templates.get(*name) {
|
if let Some(tmpl_id) = self.templates.get(*name) {
|
||||||
i.load_template(*tmpl_id, *index as u32, id.0 as u32)
|
i.load_template(*tmpl_id, *index as u16, id.0 as u32)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ReplaceWith { id, m } => i.replace_with(id.0 as u32, *m as u32),
|
ReplaceWith { id, m } => i.replace_with(id.0 as u32, *m as u16),
|
||||||
ReplacePlaceholder { path, m } => {
|
ReplacePlaceholder { path, m } => {
|
||||||
i.replace_placeholder(path.as_ptr() as u32, path.len() as u8, *m as u32)
|
i.replace_placeholder(path.as_ptr() as u32, path.len() as u8, *m as u16)
|
||||||
}
|
}
|
||||||
InsertAfter { id, m } => i.insert_after(id.0 as u32, *m as u32),
|
InsertAfter { id, m } => i.insert_after(id.0 as u32, *m as u16),
|
||||||
InsertBefore { id, m } => i.insert_before(id.0 as u32, *m as u32),
|
InsertBefore { id, m } => i.insert_before(id.0 as u32, *m as u16),
|
||||||
SetAttribute {
|
SetAttribute {
|
||||||
name,
|
name,
|
||||||
value,
|
value,
|
||||||
|
|||||||
@@ -54,6 +54,7 @@
|
|||||||
// - Do DOM work in the next requestAnimationFrame callback
|
// - Do DOM work in the next requestAnimationFrame callback
|
||||||
|
|
||||||
pub use crate::cfg::Config;
|
pub use crate::cfg::Config;
|
||||||
|
#[cfg(feature = "file_engine")]
|
||||||
pub use crate::file_engine::WebFileEngineExt;
|
pub use crate::file_engine::WebFileEngineExt;
|
||||||
use dioxus_core::{Element, Scope, VirtualDom};
|
use dioxus_core::{Element, Scope, VirtualDom};
|
||||||
use futures_util::{
|
use futures_util::{
|
||||||
|
|||||||
Reference in new issue
Block a user