wip: making progress on diffing and hydration

This commit is contained in:
Jonathan Kelley committed 2021-07-28 21:46:53 -04:00
1 parent e5c88fe3a4
commit 49856ccd68
34 files changed
+1023 -750

No files matched your search

+45
View File
@@ -0,0 +1,45 @@
use std::ops::{Deref, DerefMut};
use dioxus_core::DomEdit;
use wry::{
application::{
error::OsError,
event_loop::EventLoopWindowTarget,
menu::MenuBar,
window::{Fullscreen, Icon, Window, WindowBuilder},
},
webview::{RpcRequest, RpcResponse},
};
pub struct DesktopConfig<'a> {
pub window: WindowBuilder,
pub(crate) manual_edits: Option<DomEdit<'a>>,
pub(crate) pre_rendered: Option<String>,
}
impl DesktopConfig<'_> {
/// Initializes a new `WindowBuilder` with default values.
#[inline]
pub fn new() -> Self {
Self {
window: Default::default(),
pre_rendered: None,
manual_edits: None,
}
}
pub fn with_prerendered(&mut self, content: String) -> &mut Self {
self.pre_rendered = Some(content);
self
}
pub fn with_window(&mut self, f: impl FnOnce(WindowBuilder) -> WindowBuilder) -> &mut Self {
// gots to do a swap because the window builder only takes itself as muy self
// I wish more people knew about returning &mut Self
let mut builder = WindowBuilder::default();
std::mem::swap(&mut self.window, &mut builder);
builder = f(builder);
std::mem::swap(&mut self.window, &mut builder);
self
}
}
+207 -196
View File
@@ -2,208 +2,219 @@
<html>
<head>
<script>
class Interpreter {
constructor(root) {
this.root = root;
this.stack = [root];
this.listeners = {
"onclick": {}
};
this.lastNodeWasText = false;
this.nodes = [root, root, root, root];
}
top() {
return this.stack[this.stack.length - 1];
}
pop() {
return this.stack.pop();
}
PushRoot(edit) {
const id = edit.id;
const node = this.nodes[id];
console.log("pushing root ", node, "with id", id);
this.stack.push(node);
}
PopRoot(edit) {
this.stack.pop();
}
AppendChildren(edit) {
let root = this.stack[this.stack.length - (edit.many + 1)];
for (let i = 0; i < edit.many; i++) {
console.log("popping ", i, edit.many);
let node = this.pop();
root.appendChild(node);
}
}
ReplaceWith(edit) {
let root = this.stack[this.stack.length - (edit.many + 1)];
let els = [];
for (let i = 0; i < edit.many; i++) {
els.push(this.pop());
}
root.replaceWith(...els);
}
Remove(edit) {
const node = this.stack.pop();
node.remove();
}
RemoveAllChildren(edit) {}
CreateTextNode(edit) {
const node = document.createTextNode(edit.text);
this.nodes[edit.id] = node;
this.stack.push(node);
}
CreateElement(edit) {
const tagName = edit.tag;
const el = document.createElement(tagName);
this.nodes[edit.id] = el;
console.log(`creating element: `, edit);
this.stack.push(el);
}
CreateElementNs(edit) {
const tagName = edit.tag;
console.log(`creating namespaced element: `, edit);
this.stack.push(document.createElementNS(edit.ns, edit.tag));
}
CreatePlaceholder(edit) {
const a = `this.stack.push(document.createElement(" pre"))`;
this.stack.push(document.createComment("vroot"));
}
NewEventListener(edit) {
const element_id = edit.element_id;
const event_name = edit.event_name;
const mounted_node_id = edit.mounted_node_id;
const scope = edit.scope;
const element = this.top();
element.setAttribute(`dioxus-event-${event_name}`, `${scope}.${mounted_node_id}`);
console.log("listener map is", this.listeners);
if (this.listeners[event_name] === undefined) {
console.log("adding listener!");
this.listeners[event_name] = "bla";
this.root.addEventListener(event_name, (event) => {
const target = event.target;
const type = event.type;
const val = target.getAttribute(`dioxus-event-${event_name}`);
const fields = val.split(".");
const scope_id = parseInt(fields[0]);
const real_id = parseInt(fields[1]);
console.log(`parsed event with scope_id ${scope_id} and real_id ${real_id}`);
rpc.call('user_event', {
event: event_name,
scope: scope_id,
mounted_dom_id: real_id,
}).then((reply) => {
console.log(reply);
this.stack.push(this.root);
for (let x = 0; x < reply.length; x++) {
let edit = reply[x];
console.log(edit);
let f = this[edit.type];
f.call(this, edit);
}
console.log("initiated");
}).catch((err) => {
console.log("failed to initiate", err);
});
});
}
}
RemoveEventListener(edit) {}
SetText(edit) {
this.top().textContent = edit.text;
}
SetAttribute(edit) {
const name = edit.field;
const value = edit.value;
const ns = edit.ns;
const node = this.top(this.stack);
if (ns == "style") {
node.style[name] = value;
} else if (ns !== undefined) {
node.setAttributeNS(ns, name, value);
} else {
node.setAttribute(name, value);
}
if (name === "value") {
node.value = value;
}
if (name === "checked") {
node.checked = true;
}
if (name === "selected") {
node.selected = true;
}
}
RemoveAttribute(edit) {
const name = edit.field;
const node = this.top(this.stack);
node.removeAttribute(name);
if (name === "value") {
node.value = null;
}
if (name === "checked") {
node.checked = false;
}
if (name === "selected") {
node.selected = false;
}
}
}
async function initialize() {
const reply = await rpc.call('initiate');
const interpreter = new Interpreter(window.document.getElementById("_dioxusroot"));
console.log(reply);
for (let x = 0; x < reply.length; x++) {
let edit = reply[x];
console.log(edit);
let f = interpreter[edit.type];
f.call(interpreter, edit);
}
console.log("stack completed: ", interpreter.stack);
}
console.log("initializing...");
initialize();
</script>
</head>
<body>
<div id="_dioxusroot">
</div>
</body>
<script>
class Interpreter {
constructor(root) {
this.root = root;
this.stack = [root];
this.listeners = {
"onclick": {}
};
this.lastNodeWasText = false;
this.nodes = [root, root, root, root];
}
top() {
return this.stack[this.stack.length - 1];
}
pop() {
return this.stack.pop();
}
PushRoot(edit) {
const id = edit.id;
const node = this.nodes[id];
console.log("pushing root ", node, "with id", id);
this.stack.push(node);
}
PopRoot(edit) {
this.stack.pop();
}
AppendChildren(edit) {
let root = this.stack[this.stack.length - (edit.many + 1)];
for (let i = 0; i < edit.many; i++) {
console.log("popping ", i, edit.many);
let node = this.pop();
root.appendChild(node);
}
}
ReplaceWith(edit) {
let root = this.stack[this.stack.length - (edit.many + 1)];
let els = [];
for (let i = 0; i < edit.many; i++) {
els.push(this.pop());
}
root.replaceWith(...els);
}
Remove(edit) {
const node = this.stack.pop();
node.remove();
}
RemoveAllChildren(edit) {}
CreateTextNode(edit) {
const node = document.createTextNode(edit.text);
this.nodes[edit.id] = node;
this.stack.push(node);
}
CreateElement(edit) {
const tagName = edit.tag;
const el = document.createElement(tagName);
this.nodes[edit.id] = el;
console.log(`creating element: `, edit);
this.stack.push(el);
}
CreateElementNs(edit) {
const tagName = edit.tag;
console.log(`creating namespaced element: `, edit);
this.stack.push(document.createElementNS(edit.ns, edit.tag));
}
CreatePlaceholder(edit) {
const a = `this.stack.push(document.createElement(" pre"))`;
this.stack.push(document.createComment("vroot"));
}
NewEventListener(edit) {
const element_id = edit.element_id;
const event_name = edit.event_name;
const mounted_node_id = edit.mounted_node_id;
const scope = edit.scope;
const element = this.top();
element.setAttribute(`dioxus-event-${event_name}`, `${scope}.${mounted_node_id}`);
console.log("listener map is", this.listeners);
if (this.listeners[event_name] === undefined) {
console.log("adding listener!");
this.listeners[event_name] = "bla";
this.root.addEventListener(event_name, (event) => {
const target = event.target;
const type = event.type;
const val = target.getAttribute(`dioxus-event-${event_name}`);
const fields = val.split(".");
const scope_id = parseInt(fields[0]);
const real_id = parseInt(fields[1]);
console.log(`parsed event with scope_id ${scope_id} and real_id ${real_id}`);
rpc.call('user_event', {
event: event_name,
scope: scope_id,
mounted_dom_id: real_id,
}).then((reply) => {
console.log(reply);
this.stack.push(this.root);
let edits = reply.edits;
for (let x = 0; x < edits.length; x++) {
let edit = edits[x];
console.log(edit);
let f = this[edit.type];
f.call(this, edit);
}
console.log("initiated");
}).catch((err) => {
console.log("failed to initiate", err);
});
});
}
}
RemoveEventListener(edit) {}
SetText(edit) {
this.top().textContent = edit.text;
}
SetAttribute(edit) {
const name = edit.field;
const value = edit.value;
const ns = edit.ns;
const node = this.top(this.stack);
if (ns == "style") {
node.style[name] = value;
} else if (ns !== undefined) {
node.setAttributeNS(ns, name, value);
} else {
node.setAttribute(name, value);
}
if (name === "value") {
node.value = value;
}
if (name === "checked") {
node.checked = true;
}
if (name === "selected") {
node.selected = true;
}
}
RemoveAttribute(edit) {
const name = edit.field;
const node = this.top(this.stack);
node.removeAttribute(name);
if (name === "value") {
node.value = null;
}
if (name === "checked") {
node.checked = false;
}
if (name === "selected") {
node.selected = false;
}
}
}
async function initialize() {
const reply = await rpc.call('initiate');
let root = window.document.getElementById("_dioxusroot");
const interpreter = new Interpreter(root);
console.log(reply);
let pre_rendered = reply.pre_rendered;
if (pre_rendered !== undefined) {
root.innerHTML = pre_rendered;
}
const edits = reply.edits;
for (let x = 0; x < edits.length; x++) {
let edit = edits[x];
console.log(edit);
let f = interpreter[edit.type];
f.call(interpreter, edit);
}
console.log("stack completed: ", interpreter.stack);
}
console.log("initializing...");
initialize();
</script>
</html>
+52 -17
View File
@@ -3,6 +3,7 @@ use std::ops::{Deref, DerefMut};
use std::sync::mpsc::channel;
use std::sync::{Arc, RwLock};
use cfg::DesktopConfig;
use dioxus_core::*;
pub use wry;
@@ -15,6 +16,7 @@ use wry::{
webview::{RpcRequest, RpcResponse},
};
mod cfg;
mod dom;
mod escape;
mod events;
@@ -24,14 +26,14 @@ static HTML_CONTENT: &'static str = include_str!("./index.html");
pub fn launch(
root: FC<()>,
builder: impl FnOnce(WindowBuilder) -> WindowBuilder,
builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>,
) -> anyhow::Result<()> {
launch_with_props(root, (), builder)
}
pub fn launch_with_props<P: Properties + 'static>(
root: FC<P>,
props: P,
builder: impl FnOnce(WindowBuilder) -> WindowBuilder,
builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>,
) -> anyhow::Result<()> {
WebviewRenderer::run(root, props, builder)
}
@@ -46,11 +48,17 @@ enum RpcEvent<'a> {
Initialize { edits: Vec<DomEdit<'a>> },
}
#[derive(Serialize)]
struct Response<'a> {
pre_rendered: Option<String>,
edits: Vec<DomEdit<'a>>,
}
impl<T: Properties + 'static> WebviewRenderer<T> {
pub fn run(
root: FC<T>,
props: T,
user_builder: impl FnOnce(WindowBuilder) -> WindowBuilder,
user_builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>,
) -> anyhow::Result<()> {
Self::run_with_edits(root, props, user_builder, None)
}
@@ -58,13 +66,22 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
pub fn run_with_edits(
root: FC<T>,
props: T,
user_builder: impl FnOnce(WindowBuilder) -> WindowBuilder,
user_builder: impl for<'a, 'b> FnOnce(&'a mut DesktopConfig<'b>) -> &'a mut DesktopConfig<'b>,
redits: Option<Vec<DomEdit<'static>>>,
) -> anyhow::Result<()> {
log::info!("hello edits");
let event_loop = EventLoop::new();
let window = user_builder(WindowBuilder::new()).build(&event_loop)?;
let mut cfg = DesktopConfig::new();
user_builder(&mut cfg);
let DesktopConfig {
window,
manual_edits,
pre_rendered,
} = cfg;
let window = window.build(&event_loop)?;
let vir = VirtualDom::new_with_props(root, props);
@@ -73,8 +90,6 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
// let registry = Arc::new(RwLock::new(Some(WebviewRegistry::new())));
let webview = WebViewBuilder::new(window)?
// .with_visible(false)
// .with_transparent(true)
.with_url(&format!("data:text/html,{}", HTML_CONTENT))?
.with_rpc_handler(move |_window: &Window, mut req: RpcRequest| {
match req.method.as_str() {
@@ -87,17 +102,32 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
// Create the thin wrapper around the registry to collect the edits into
let mut real = dom::WebviewDom::new();
let pre = pre_rendered.clone();
// Serialize the edit stream
let edits = {
let mut edits = Vec::new();
lock.rebuild(&mut real, &mut edits).unwrap();
serde_json::to_value(edits).unwrap()
let response = match pre {
Some(content) => {
lock.rebuild_in_place().unwrap();
Response {
edits: Vec::new(),
pre_rendered: Some(content),
}
}
None => {
//
let edits = {
let mut edits = Vec::new();
lock.rebuild(&mut real, &mut edits).unwrap();
edits
};
Response {
edits,
pre_rendered: None,
}
}
};
// Give back the registry into its slot
// *reg_lock = Some(real.consume());
edits
serde_json::to_value(&response).unwrap()
};
// Return the edits into the webview runtime
@@ -128,13 +158,18 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
lock.progress_with_event(&mut real, &mut edits)
.await
.expect("failed to progress");
let edits = serde_json::to_value(edits).unwrap();
let response = Response {
edits,
pre_rendered: None,
};
let response = serde_json::to_value(&response).unwrap();
// Give back the registry into its slot
// *reg_lock = Some(real.consume());
// Return the edits into the webview runtime
Some(RpcResponse::new_result(req.id.take(), Some(edits)))
Some(RpcResponse::new_result(req.id.take(), Some(response)))
});
response