Feat: move webview logic into library
This commit is contained in:
1 parent
18a7a1f9c4
commit
32b45e5ba1
10 files changed
+249
-514
No files matched your search
@@ -0,0 +1,289 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
|
||||
<meta charset="UTF-8" />
|
||||
<link
|
||||
href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
</head>
|
||||
<body>
|
||||
<div></div>
|
||||
</body>
|
||||
<script>
|
||||
class Interpreter {
|
||||
constructor(root) {
|
||||
this.stack = [root];
|
||||
}
|
||||
|
||||
top() {
|
||||
return this.stack[this.stack.length - 1];
|
||||
}
|
||||
|
||||
pop() {
|
||||
return this.stack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
class OPTABLE {
|
||||
// 0
|
||||
SetText(edit, interp) {
|
||||
interp.top(interp.stack).textContent = edit.text;
|
||||
}
|
||||
// 1
|
||||
RemoveSelfAndNextSiblings(edit) {
|
||||
const node = interp.pop();
|
||||
let sibling = node.nextSibling;
|
||||
while (sibling) {
|
||||
const temp = sibling.nextSibling;
|
||||
sibling.remove();
|
||||
sibling = temp;
|
||||
}
|
||||
node.remove();
|
||||
}
|
||||
|
||||
// 2
|
||||
ReplaceWith(edit, interp) {
|
||||
const newNode = interp.pop();
|
||||
const oldNode = interp.pop();
|
||||
oldNode.replaceWith(newNode);
|
||||
interp.stack.push(newNode);
|
||||
}
|
||||
|
||||
// 3
|
||||
SetAttribute(edit, interp) {
|
||||
const name = edit.name;
|
||||
const value = edit.value;
|
||||
const node = interp.top(interp.stack);
|
||||
node.setAttribute(name, value);
|
||||
|
||||
// Some attributes are "volatile" and don't work through `setAttribute`.
|
||||
if ((name === "value", interp)) {
|
||||
node.value = value;
|
||||
}
|
||||
if ((name === "checked", interp)) {
|
||||
node.checked = true;
|
||||
}
|
||||
if ((name === "selected", interp)) {
|
||||
node.selected = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 4
|
||||
RemoveAttribute(edit, interp) {
|
||||
const name = edit.name;
|
||||
const node = interp.top(interp.stack);
|
||||
node.removeAttribute(name);
|
||||
|
||||
// Some attributes are "volatile" and don't work through `removeAttribute`.
|
||||
if ((name === "value", interp)) {
|
||||
node.value = null;
|
||||
}
|
||||
if ((name === "checked", interp)) {
|
||||
node.checked = false;
|
||||
}
|
||||
if ((name === "selected", interp)) {
|
||||
node.selected = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 5
|
||||
PushReverseChild(edit, interp) {
|
||||
const n = edit.n;
|
||||
const parent = interp.top(interp.stack);
|
||||
const children = parent.childNodes;
|
||||
const child = children[children.length - n - 1];
|
||||
interp.stack.push(child);
|
||||
}
|
||||
|
||||
// 6
|
||||
PopPushChild(edit, interp) {
|
||||
const n = edit.n;
|
||||
interp.pop();
|
||||
const parent = interp.top(interp.stack);
|
||||
const children = parent.childNodes;
|
||||
const child = children[n];
|
||||
interp.stack.push(child);
|
||||
}
|
||||
|
||||
// 7
|
||||
Pop(edit, interp) {
|
||||
interp.pop();
|
||||
}
|
||||
|
||||
// 8
|
||||
AppendChild(edit, interp) {
|
||||
console.log(interp.stack);
|
||||
const child = interp.pop();
|
||||
console.log(interp.stack);
|
||||
interp.top().appendChild(child);
|
||||
}
|
||||
|
||||
// 9
|
||||
CreateTextNode(edit, interp) {
|
||||
// interp.stack.push(document.createTextNode("asd"));
|
||||
console.log(interp.stack);
|
||||
|
||||
interp.stack.push(document.createTextNode(edit.text));
|
||||
console.log(interp.stack);
|
||||
}
|
||||
|
||||
// 10
|
||||
CreateElement(edit, interp) {
|
||||
const tagName = edit.tag_name;
|
||||
interp.stack.push(document.createElement(tagName));
|
||||
}
|
||||
|
||||
// 11
|
||||
NewEventListener(edit, interp) {
|
||||
// todo!
|
||||
const eventId = mem32[i++];
|
||||
const eventType = interp.getCachedString(eventId);
|
||||
const a = mem32[i++];
|
||||
const b = mem32[i++];
|
||||
const el = interp.top(interp.stack);
|
||||
el.addEventListener(eventType, interp.eventHandler);
|
||||
el[`dodrio-a-${eventType}`] = a;
|
||||
el[`dodrio-b-${eventType}`] = b;
|
||||
}
|
||||
|
||||
// 12
|
||||
UpdateEventListener(edit, interp) {
|
||||
// todo!
|
||||
const eventId = mem32[i++];
|
||||
const eventType = interp.getCachedString(eventId);
|
||||
const el = interp.top(interp.stack);
|
||||
el[`dodrio-a-${eventType}`] = mem32[i++];
|
||||
el[`dodrio-b-${eventType}`] = mem32[i++];
|
||||
}
|
||||
|
||||
// 13
|
||||
RemoveEventListener(edit, interp) {
|
||||
// todo!
|
||||
const eventId = mem32[i++];
|
||||
const eventType = interp.getCachedString(eventId);
|
||||
const el = interp.top(interp.stack);
|
||||
el.removeEventListener(eventType, interp.eventHandler);
|
||||
}
|
||||
|
||||
// 14
|
||||
AddCachedString(edit, interp) {
|
||||
// todo!
|
||||
const pointer = mem32[i++];
|
||||
const length = mem32[i++];
|
||||
const id = mem32[i++];
|
||||
const str = string(mem8, pointer, length);
|
||||
interp.addCachedString(str, id);
|
||||
}
|
||||
|
||||
// 15
|
||||
DropCachedString(edit, interp) {
|
||||
// todo!
|
||||
const id = mem32[i++];
|
||||
interp.dropCachedString(id);
|
||||
}
|
||||
|
||||
// 16
|
||||
CreateElementNS(edit, interp) {
|
||||
// const tagNameId = mem32[i++];
|
||||
// const tagName = interp.getCachedString(tagNameId);
|
||||
// const nsId = mem32[i++];
|
||||
// const ns = interp.getCachedString(nsId);
|
||||
interp.stack.push(document.createElementNS(edit.ns, edit.tag_name));
|
||||
}
|
||||
|
||||
// 17
|
||||
SaveChildrenToTemporaries(edit, interp) {
|
||||
// let temp = mem32[i++];
|
||||
// const start = mem32[i++];
|
||||
// const end = mem32[i++];
|
||||
let temp = edit.temp;
|
||||
const start = edit.start;
|
||||
const end = edit.end;
|
||||
const parent = interp.top(interp.stack);
|
||||
const children = parent.childNodes;
|
||||
for (let i = start; i < end; i++, interp) {
|
||||
interp.temporaries[temp++] = children[i];
|
||||
}
|
||||
}
|
||||
|
||||
// 18
|
||||
PushChild(edit, interp) {
|
||||
const parent = interp.top(interp.stack);
|
||||
// const n = mem32[i++];
|
||||
const n = edit.n;
|
||||
const child = parent.childNodes[n];
|
||||
interp.stack.push(child);
|
||||
}
|
||||
|
||||
// 19
|
||||
PushTemporary(edit, interp) {
|
||||
// const temp = mem32[i++];
|
||||
const temp = edit.temp;
|
||||
interp.stack.push(interp.temporaries[temp]);
|
||||
}
|
||||
|
||||
// 20
|
||||
InsertBefore(edit, interp) {
|
||||
const before = interp.pop();
|
||||
const after = interp.pop();
|
||||
after.parentNode.insertBefore(before, after);
|
||||
interp.stack.push(before);
|
||||
}
|
||||
|
||||
// 21
|
||||
PopPushReverseChild(edit, interp) {
|
||||
// const n = mem32[i++];
|
||||
const n = edit.n;
|
||||
interp.pop();
|
||||
const parent = interp.top(interp.stack);
|
||||
const children = parent.childNodes;
|
||||
const child = children[children.length - n - 1];
|
||||
interp.stack.push(child);
|
||||
}
|
||||
|
||||
// 22
|
||||
RemoveChild(edit, interp) {
|
||||
// const n = mem32[i++];
|
||||
const n = edit.n;
|
||||
const parent = interp.top(interp.stack);
|
||||
const child = parent.childNodes[n];
|
||||
child.remove();
|
||||
}
|
||||
|
||||
// 23
|
||||
SetClass(edit, interp) {
|
||||
// const classId = mem32[i++];
|
||||
const className = edit.class_name;
|
||||
interp.top(interp.stack).className = className;
|
||||
}
|
||||
|
||||
// 24
|
||||
SaveTemplate(edit, interp) {
|
||||
const id = mem32[i++];
|
||||
const template = interp.top(interp.stack);
|
||||
interp.saveTemplate(id, template.cloneNode(true));
|
||||
}
|
||||
|
||||
// 25
|
||||
PushTemplate(edit, interp) {
|
||||
const id = mem32[i++];
|
||||
const template = interp.getTemplate(id);
|
||||
interp.stack.push(template.cloneNode(true));
|
||||
}
|
||||
}
|
||||
|
||||
const op_table = new OPTABLE();
|
||||
const interpreter = new Interpreter(window.document.body);
|
||||
|
||||
function EditListReceived(rawEditList) {
|
||||
let editList = JSON.parse(rawEditList);
|
||||
editList.forEach(function (edit, index) {
|
||||
op_table[edit.type](edit, interpreter);
|
||||
});
|
||||
}
|
||||
|
||||
external.invoke("initiate");
|
||||
</script>
|
||||
</html>
|
||||
+167
-41
@@ -1,8 +1,18 @@
|
||||
use dioxus_core::prelude::*;
|
||||
use web_view::WebViewBuilder;
|
||||
use std::sync::mpsc::channel;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub fn new<T>(root: FC<T>) -> WebviewRenderer<T> {
|
||||
WebviewRenderer::new(root)
|
||||
use dioxus_core::prelude::*;
|
||||
use web_view::Handle;
|
||||
use web_view::{WVResult, WebView, WebViewBuilder};
|
||||
|
||||
static HTML_CONTENT: &'static str = include_str!("./index.html");
|
||||
|
||||
pub fn launch<T: 'static>(
|
||||
builder: impl FnOnce(DioxusWebviewBuilder) -> DioxusWebviewBuilder,
|
||||
props: T,
|
||||
root: FC<T>,
|
||||
) -> anyhow::Result<()> {
|
||||
WebviewRenderer::run(root, props, builder)
|
||||
}
|
||||
|
||||
/// The `WebviewRenderer` provides a way of rendering a Dioxus Virtual DOM through a bridge to a Webview instance.
|
||||
@@ -11,13 +21,78 @@ pub struct WebviewRenderer<T> {
|
||||
/// The root component used to render the Webview
|
||||
root: FC<T>,
|
||||
}
|
||||
enum InnerEvent {
|
||||
Initiate(Handle<()>),
|
||||
}
|
||||
|
||||
impl<T: 'static> WebviewRenderer<T> {
|
||||
pub fn run(
|
||||
root: FC<T>,
|
||||
props: T,
|
||||
user_builder: impl FnOnce(DioxusWebviewBuilder) -> DioxusWebviewBuilder,
|
||||
) -> anyhow::Result<()> {
|
||||
let (sender, receiver) = channel::<InnerEvent>();
|
||||
|
||||
let DioxusWebviewBuilder {
|
||||
title,
|
||||
width,
|
||||
height,
|
||||
resizable,
|
||||
debug,
|
||||
frameless,
|
||||
visible,
|
||||
min_width,
|
||||
min_height,
|
||||
} = user_builder(DioxusWebviewBuilder::new());
|
||||
|
||||
let mut view = web_view::builder()
|
||||
.invoke_handler(|view, arg| {
|
||||
let handle = view.handle();
|
||||
sender
|
||||
.send(InnerEvent::Initiate(handle))
|
||||
.expect("should not fail");
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.content(web_view::Content::Html(HTML_CONTENT))
|
||||
.user_data(())
|
||||
.title(title)
|
||||
.size(width, height)
|
||||
.resizable(resizable)
|
||||
.debug(debug)
|
||||
.frameless(frameless)
|
||||
.visible(visible)
|
||||
.min_size(min_width, min_height)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let mut vdom = VirtualDom::new_with_props(root, props);
|
||||
let edits = vdom.rebuild()?;
|
||||
let ref_edits = Arc::new(serde_json::to_string(&edits)?);
|
||||
|
||||
loop {
|
||||
view.step()
|
||||
.expect("should not fail")
|
||||
.expect("should not fail");
|
||||
|
||||
if let Ok(event) = receiver.try_recv() {
|
||||
if let InnerEvent::Initiate(handle) = event {
|
||||
let editlist = ref_edits.clone();
|
||||
handle
|
||||
.dispatch(move |view| {
|
||||
view.eval(format!("EditListReceived(`{}`);", editlist).as_str())
|
||||
})
|
||||
.expect("Dispatch failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> WebviewRenderer<T> {
|
||||
/// Create a new text-renderer instance from a functional component root.
|
||||
/// Automatically progresses the creation of the VNode tree to completion.
|
||||
///
|
||||
/// A VDom is automatically created. If you want more granular control of the VDom, use `from_vdom`
|
||||
pub fn new(root: FC<T>) -> Self {
|
||||
pub fn new(root: FC<T>, builder: impl FnOnce() -> WVResult<WebView<'static, ()>>) -> Self {
|
||||
Self { root }
|
||||
}
|
||||
|
||||
@@ -36,41 +111,92 @@ impl<T> WebviewRenderer<T> {
|
||||
pub fn update_mut(&mut self, modifier: impl Fn(&mut T)) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn launch(self, props: T) {
|
||||
todo!()
|
||||
// let mut ctx = Context { props: &props };
|
||||
// let WebviewRenderer { root } = self;
|
||||
// let content = root(&mut ctx);
|
||||
// let html_content = content.to_string();
|
||||
// /*
|
||||
// TODO: @Jon
|
||||
// Launch the webview with a premade VDom app
|
||||
// */
|
||||
|
||||
// web_view::builder()
|
||||
// .title("My Project")
|
||||
// .content(web_view::Content::Html(html_content))
|
||||
// .size(320, 480)
|
||||
// .resizable(true)
|
||||
// .debug(true)
|
||||
// .user_data(())
|
||||
// .invoke_handler(|_webview, _arg| Ok(()))
|
||||
// .run()
|
||||
// .unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// mod receiver {
|
||||
// use dioxus_core::prelude::*;
|
||||
pub struct DioxusWebviewBuilder<'a> {
|
||||
pub(crate) title: &'a str,
|
||||
pub(crate) width: i32,
|
||||
pub(crate) height: i32,
|
||||
pub(crate) resizable: bool,
|
||||
pub(crate) debug: bool,
|
||||
pub(crate) frameless: bool,
|
||||
pub(crate) visible: bool,
|
||||
pub(crate) min_width: i32,
|
||||
pub(crate) min_height: i32,
|
||||
}
|
||||
impl<'a> DioxusWebviewBuilder<'a> {
|
||||
fn new() -> Self {
|
||||
#[cfg(debug_assertions)]
|
||||
let debug = true;
|
||||
#[cfg(not(debug_assertions))]
|
||||
let debug = false;
|
||||
|
||||
// /// The receiver app is a container that builds a connection to the host process that shuttles events and patches.
|
||||
// pub(crate) static ReceiverApp: FC<()> = |ctx| {
|
||||
// //
|
||||
// html! {
|
||||
// <div>
|
||||
// {}
|
||||
// </div>
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
DioxusWebviewBuilder {
|
||||
title: "Application",
|
||||
width: 800,
|
||||
height: 600,
|
||||
resizable: true,
|
||||
debug,
|
||||
frameless: false,
|
||||
visible: true,
|
||||
min_width: 300,
|
||||
min_height: 300,
|
||||
}
|
||||
}
|
||||
/// Sets the title of the WebView window.
|
||||
///
|
||||
/// Defaults to `"Application"`.
|
||||
pub fn title(mut self, title: &'a str) -> Self {
|
||||
self.title = title;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the size of the WebView window.
|
||||
///
|
||||
/// Defaults to 800 x 600.
|
||||
pub fn size(mut self, width: i32, height: i32) -> Self {
|
||||
self.width = width;
|
||||
self.height = height;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the resizability of the WebView window. If set to false, the window cannot be resized.
|
||||
///
|
||||
/// Defaults to `true`.
|
||||
pub fn resizable(mut self, resizable: bool) -> Self {
|
||||
self.resizable = resizable;
|
||||
self
|
||||
}
|
||||
|
||||
/// Enables or disables debug mode.
|
||||
///
|
||||
/// Defaults to `true` for debug builds, `false` for release builds.
|
||||
pub fn debug(mut self, debug: bool) -> Self {
|
||||
self.debug = debug;
|
||||
self
|
||||
}
|
||||
/// The window crated will be frameless
|
||||
///
|
||||
/// defaults to `false`
|
||||
pub fn frameless(mut self, frameless: bool) -> Self {
|
||||
self.frameless = frameless;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the visibility of the WebView window.
|
||||
///
|
||||
/// defaults to `true`
|
||||
pub fn visible(mut self, visible: bool) -> Self {
|
||||
self.visible = visible;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the minimum size of the WebView window.
|
||||
///
|
||||
/// Defaults to 300 x 300.
|
||||
pub fn min_size(mut self, width: i32, height: i32) -> Self {
|
||||
self.min_width = width;
|
||||
self.min_height = height;
|
||||
self
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user