chore: cleanup workspace

This commit is contained in:
Jonathan Kelley committed 2021-07-15 18:40:12 -04:00
1 parent 3bedcb93ca
commit 8f0bb5dc5b
54 files changed
+691 -1822

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
{
"rust-analyzer.inlayHints.enable": false
}
+14
View File
@@ -0,0 +1,14 @@
# dioxus webview arch
dioxus webview represents a usecase of dioxus to stream edits from one dom to another. In this particular case, we stream edits from a native process into the webview's runtime. WebView is really portable, so we can leverage the UI of iOS, Android, and all desktop platforms while also maintaining direct access to platform-specific APIs. It's the best of both worlds!
For this to work properly, we need to ship a universal client that receives Dioxus edits from a remote process. To make our lives (as developers) a bit easier, the client code is currently implemented in JS (subject to change!).
The provider (the thing that talks to the client on the other side of the network) is still written in Rust, and is supplied through the dioxus-webview crate. The client is much more "dumb" than the provider - the provider must handle NodeID generation, flushing changes, priorities, managing events, and more.
## update; wry!
Wry is an effort by the Tauri team to make fully native
+41
View File
@@ -0,0 +1,41 @@
[package]
name = "dioxus-desktop"
version = "0.0.0"
authors = ["Jonathan Kelley <jkelleyrtp@gmail.com>"]
edition = "2018"
description = "Dioxus VirtualDOM renderer for a remote webview instance"
license = "MIT/Apache-2.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
# web-view = { git = "https://github.com/Boscop/web-view" }
dioxus-core = { path = "../core", version = "0.1.2", features = ["serialize"] }
anyhow = "1.0.38"
argh = "0.1.4"
serde = "1.0.120"
serde_json = "1.0.61"
thiserror = "1.0.23"
log = "0.4.13"
fern = { version = "0.6.0", features = ["colored"] }
html-escape = "0.2.9"
wry = "0.11.0"
[dev-dependencies]
tide = "0.15.0"
tide-websockets = "0.3.0"
async-std = { version = "1.9.0", features = ["attributes"] }
# thiserror = "1.0.23"
# log = "0.4.13"
# fern = { version = "0.6.0", features = ["colored"] }
# wasm-bindgen-cli-support = "0.2.71"
# anyhow = "1.0.38"
# argh = "0.1.4"
# async-std = { version = "1.9.0", features = ["attributes"] }
# serde = "1.0.120"
# serde_json = "1.0.61"
[build-dependencies]
+47
View File
@@ -0,0 +1,47 @@
# Dioxus-webview
Dioxus-webview bridges virtual and Webview DOMs together to make simple, portable, desktop applications.
Dioxus-webview is an attempt at making a simpler "Tauri" where creating desktop applications is as simple as:
```rust
// main.rs
#[async_std::main]
async fn main() {
dioxus_webview::new(|cx| {
let (count, set_count) = use_state(cx, || 0);
cx.render(html! {
<div>
<h1> "Dioxus Desktop Demo" </h1>
<p> "Count is {count}"</p>
<button onclick=|_| set_count(count + 1) >
"Click to increment"
</button>
</div>
})
})
.configure_webview(|view| {
// custom webview config options
})
.launch()
.await;
}
```
and then to create a native .app:
```
dioxus bundle --platform macOS
```
## Goals
Because the host VirtualDOM is running in its own native process, native applications can unlock their full potential. Dioxus-webview is designed to be a 100% rust alternative to ElectronJS without the memory overhead or bloat of ElectronJS apps.
By bridging the native process, desktop apps can access full multithreading power, peripheral support, hardware access, and native filesystem controls without the hassle of web technologies. Our goal with Dioxus-webview is to make it easy to ship both a web and native application, and quickly see large performance boosts without having to re-write the whole stack. As the dioxus ecosystem grows, we hope to see 3rd parties providing wrappers for storage, offline mode, etc that supports both web and native technologies.
## Tech
Dioxus-desktop is a pure liveview application where all of the state and event handlers are proxied through the liveview and into the native process. For pure server-based liveview, this would normally be too slow (in both render performance and latency), but because the VDom is local, desktop apps are just as fast as Electron.
Dioxus-desktop leverages dioxus-liveview under the hood, but with convenience wrappers around setting up the VDom bridge, proxying events, and serving the initial WebSys-Renderer. The backend is served by Tide, so an async runtime _is_ needed - we recommend async-std in Tokio mode.
+10
View File
@@ -0,0 +1,10 @@
[package]
name = "webview-client"
version = "0.1.0"
authors = ["Jonathan Kelley <jkelleyrtp@gmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
dioxus-core = {path = "../../core"}
+64
View File
@@ -0,0 +1,64 @@
use dioxus_core::patch::Edit;
use dioxus_core::prelude::*;
static SERVER_RENDERED_KEY: &'static str = "abc123";
#[derive(Debug, PartialEq, Props)]
struct ServerRendered {
name0: String,
name1: String,
name2: String,
name3: String,
name4: String,
name5: String,
name6: String,
name7: String,
}
fn main() {}
#[cfg(old)]
fn blah() {
// connect to the host server
let server_rendered = use_server_rendered((111111, 11111, 11), SERVER_RENDERED_KEY, || {
ServerRendered {
name0: "abc".to_string(),
name1: "abc".to_string(),
name2: "abc".to_string(),
name3: "abc".to_string(),
name4: "abc".to_string(),
name5: "abc".to_string(),
name6: "abc".to_string(),
name7: "abc".to_string(),
}
});
let handler = dioxus_liveview::new_handler()
.from_directory("abc123") // serve a given directory as the root
.with_context(|| SomeContext {}) // build out a new context for all of the server-rendered components to share
.with_route(SERVER_RENDERED_KEY, |cx: &ServerRendered| {
//
})
.with_route(SERVER_RENDERED_KEY, |cx| {
//
})
.with_route(SERVER_RENDERED_KEY, |cx| {
//
})
.with_route(SERVER_RENDERED_KEY, |cx| {
//
})
.with_route(SERVER_RENDERED_KEY, |cx| {
//
})
.with_route(SERVER_RENDERED_KEY, |cx| {
//
})
// depend on the framework, build a different type of handler
// there are instructions on how to integrate this the various rusty web frameworks in the guide
.build();
server.get("abc", handler);
}
fn use_server_rendered<F: Properties>(_p: impl PartialEq, name: &'static str, f: impl Fn() -> F) {}
+25
View File
@@ -0,0 +1,25 @@
use dioxus_core as dioxus;
use dioxus_core::prelude::*;
fn main() {
dioxus_webview::launch(App, |f| f.with_maximized(true)).expect("Failed");
}
static App: FC<()> = |cx| {
//
cx.render(rsx!(
div {
"hello world!"
}
))
};
mod dioxus_elements {
use super::*;
pub struct div;
impl DioxusElement for div {
const TAG_NAME: &'static str = "div";
const NAME_SPACE: Option<&'static str> = None;
}
pub trait GlobalAttributes {}
}
+43
View File
@@ -0,0 +1,43 @@
//! webview dom
use dioxus_core::{DomEdit, RealDom, RealDomNode, ScopeId};
use DomEdit::*;
pub struct WebviewRegistry {}
impl WebviewRegistry {
pub fn new() -> Self {
Self {}
}
}
pub struct WebviewDom<'bump> {
pub edits: Vec<DomEdit<'bump>>,
pub node_counter: u64,
pub registry: WebviewRegistry,
}
impl WebviewDom<'_> {
pub fn new(registry: WebviewRegistry) -> Self {
Self {
edits: Vec::new(),
node_counter: 0,
registry,
}
}
// Finish using the dom (for its edit list) and give back the node and event registry
pub fn consume(self) -> WebviewRegistry {
self.registry
}
}
impl<'bump> RealDom<'bump> for WebviewDom<'bump> {
fn raw_node_as_any(&self) -> &mut dyn std::any::Any {
todo!()
// self.edits.push(PushRoot { root });
}
fn request_available_node(&mut self) -> RealDomNode {
self.node_counter += 1;
RealDomNode::from_u64(self.node_counter)
}
}
+81
View File
@@ -0,0 +1,81 @@
use std::fmt::{self, Write};
/// Escape a string to pass it into JavaScript.
///
/// # Example
///
/// ```rust,no_run
/// # use web_view::WebView;
/// # use std::mem;
/// #
/// # let mut view: WebView<()> = unsafe { mem::uninitialized() };
/// #
/// let string = "Hello, world!";
///
/// // Calls the function callback with "Hello, world!" as its parameter.
///
/// view.eval(&format!("callback({});", web_view::escape(string)));
/// ```
pub fn escape(string: &str) -> Escaper {
Escaper(string)
}
// "All code points may appear literally in a string literal except for the
// closing quote code points, U+005C (REVERSE SOLIDUS), U+000D (CARRIAGE
// RETURN), U+2028 (LINE SEPARATOR), U+2029 (PARAGRAPH SEPARATOR), and U+000A
// (LINE FEED)." - ES6 Specification
pub struct Escaper<'a>(&'a str);
const SPECIAL: &[char] = &[
'\n', // U+000A (LINE FEED)
'\r', // U+000D (CARRIAGE RETURN)
'\'', // U+0027 (APOSTROPHE)
'\\', // U+005C (REVERSE SOLIDUS)
'\u{2028}', // U+2028 (LINE SEPARATOR)
'\u{2029}', // U+2029 (PARAGRAPH SEPARATOR)
];
impl<'a> fmt::Display for Escaper<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let &Escaper(mut string) = self;
f.write_char('\'')?;
while !string.is_empty() {
if let Some(i) = string.find(SPECIAL) {
if i > 0 {
f.write_str(&string[..i])?;
}
let mut chars = string[i..].chars();
f.write_str(match chars.next().unwrap() {
'\n' => "\\n",
'\r' => "\\r",
'\'' => "\\'",
'\\' => "\\\\",
'\u{2028}' => "\\u2028",
'\u{2029}' => "\\u2029",
_ => unreachable!(),
})?;
string = chars.as_str();
} else {
f.write_str(string)?;
break;
}
}
f.write_char('\'')?;
Ok(())
}
}
#[test]
fn test() {
let plain = "ABC \n\r' abc \\ \u{2028} \u{2029}123";
let escaped = escape(plain).to_string();
assert!(escaped == "'ABC \\n\\r\\' abc \\\\ \\u2028 \\u2029123'");
}
+139
View File
@@ -0,0 +1,139 @@
<!DOCTYPE html>
<html>
<head>
<script>
class Interpreter {
constructor(root) {
this.stack = [root];
this.listeners = {};
this.lastNodeWasText = false;
this.nodes = {
0: root
};
}
top() {
return this.stack[this.stack.length - 1];
}
pop() {
return this.stack.pop();
}
}
class OPTABLE {
PushRoot(self, edit) {
const id = edit.root;
const node = self.nodes[id];
self.stack.push(node);
}
AppendChildren(self, edit) {
let root = self.stack[self.stack.length - (edit.many + 1)];
for (let i = 0; i < edit.many; i++) {
console.log("popping ", i, edit.many);
let node = self.pop();
root.appendChild(node);
}
}
ReplaceWith(self, edit) {
let root = self.stack[self.stack.length - (edit.many + 1)];
let els = [];
for (let i = 0; i < edit.many; i++) {
els.push(self.pop());
}
root.replaceWith(...els);
}
Remove(self, edit) {
const node = self.stack.pop();
node.remove();
}
RemoveAllChildren(self, edit) {
}
CreateTextNode(self, edit) {
self.stack.push(document.createTextNode(edit.text));
}
CreateElement(self, edit) {
const tagName = edit.tag;
console.log(`creating element: `, edit);
self.stack.push(document.createElement(tagName));
}
CreateElementNs(self, edit) {
self.stack.push(document.createElementNS(edit.ns, edit.tag));
}
CreatePlaceholder(self, edit) {
const a = `self.stack.push(document.createElement("pre"))`;
self.stack.push(document.createComment("vroot"));
}
NewEventListener(self, edit) {
}
RemoveEventListener(self, edit) {
}
SetText(self, edit) {
self.top().textContent = edit.text;
}
SetAttribute(self, edit) {
const name = edit.field;
const value = edit.value;
const node = self.top(self.stack);
node.setAttribute(name, value);
if ((name === "value", self)) {
node.value = value;
}
if ((name === "checked", self)) {
node.checked = true;
}
if ((name === "selected", self)) {
node.selected = true;
}
}
RemoveAttribute(self, edit) {
const name = edit.field;
const node = self.top(self.stack);
node.removeAttribute(name);
if ((name === "value", self)) {
node.value = null;
}
if ((name === "checked", self)) {
node.checked = false;
}
if ((name === "selected", self)) {
node.selected = false;
}
}
}
const op_table = new OPTABLE();
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);
op_table[edit.type](interpreter, edit);
}
console.log("stack completed: ", interpreter.stack);
}
console.log("initializing...");
initialize();
</script>
</head>
<body>
<div id="_dioxusroot">
</div>
</body>
</html>
+230
View File
@@ -0,0 +1,230 @@
use std::borrow::BorrowMut;
use std::ops::{Deref, DerefMut};
use std::sync::mpsc::channel;
use std::sync::{Arc, RwLock};
use dioxus_core::*;
pub use wry;
use wry::application::event::{Event, WindowEvent};
use wry::application::event_loop::{ControlFlow, EventLoop};
use wry::application::window::Fullscreen;
use wry::webview::WebViewBuilder;
use wry::{
application::window::{Window, WindowBuilder},
webview::{RpcRequest, RpcResponse},
};
mod dom;
mod escape;
static HTML_CONTENT: &'static str = include_str!("./index.html");
pub fn launch(
root: FC<()>,
builder: impl FnOnce(WindowBuilder) -> WindowBuilder,
) -> 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,
) -> 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.
/// Components used in WebviewRenderer instances can directly use system libraries, access the filesystem, and multithread with ease.
pub struct WebviewRenderer<T> {
/// The root component used to render the Webview
root: FC<T>,
}
enum RpcEvent<'a> {
Initialize {
//
edits: Vec<DomEdit<'a>>,
},
}
impl<T: Properties + 'static> WebviewRenderer<T> {
pub fn run(
root: FC<T>,
props: T,
user_builder: impl FnOnce(WindowBuilder) -> WindowBuilder,
) -> anyhow::Result<()> {
Self::run_with_edits(root, props, user_builder, None)
}
pub fn run_with_edits(
root: FC<T>,
props: T,
user_builder: impl FnOnce(WindowBuilder) -> WindowBuilder,
redits: Option<Vec<DomEdit<'static>>>,
) -> anyhow::Result<()> {
let event_loop = EventLoop::new();
let window = user_builder(WindowBuilder::new()).build(&event_loop)?;
let vir = VirtualDom::new_with_props(root, props);
// todo: combine these or something
let vdom = Arc::new(RwLock::new(vir));
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() {
"initiate" => {
let edits = if let Some(edits) = &redits {
serde_json::to_value(edits).unwrap()
} else {
let mut lock = vdom.write().unwrap();
let mut reg_lock = registry.write().unwrap();
// Create the thin wrapper around the registry to collect the edits into
let mut real = dom::WebviewDom::new(reg_lock.take().unwrap());
// Serialize the edit stream
let edits = {
let mut edits = Vec::new();
lock.rebuild(&mut real, &mut edits).unwrap();
serde_json::to_value(edits).unwrap()
};
// Give back the registry into its slot
*reg_lock = Some(real.consume());
edits
};
// Return the edits into the webview runtime
Some(RpcResponse::new_result(req.id.take(), Some(edits)))
}
"user_event" => {
let mut lock = vdom.write().unwrap();
let mut reg_lock = registry.write().unwrap();
// Create the thin wrapper around the registry to collect the edits into
let mut real = dom::WebviewDom::new(reg_lock.take().unwrap());
// Serialize the edit stream
let edits = {
let mut edits = Vec::new();
lock.rebuild(&mut real, &mut edits).unwrap();
serde_json::to_value(edits).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)))
}
_ => todo!("this message failed"),
}
})
.build()?;
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Poll;
match event {
Event::WindowEvent {
event, window_id, ..
} => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::Resized(_) | WindowEvent::Moved(_) => {
let _ = webview.resize();
}
_ => {}
},
Event::MainEventsCleared => {
webview.resize();
// window.request_redraw();
}
_ => {} // Event::WindowEvent { event, .. } => {
// //
// match event {
// WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
// _ => {}
// }
// }
// _ => {
// // let _ = webview.resize();
// }
}
});
}
/// 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>, builder: impl FnOnce() -> WVResult<WebView<'static, ()>>) -> Self {
// Self { root }
// }
/// Create a new text renderer from an existing Virtual DOM.
/// This will progress the existing VDom's events to completion.
pub fn from_vdom() -> Self {
todo!()
}
/// Pass new args to the root function
pub fn update(&mut self, _new_val: T) {
todo!()
}
/// Modify the root function in place, forcing a re-render regardless if the props changed
pub fn update_mut(&mut self, _modifier: impl Fn(&mut T)) {
todo!()
}
}
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::dom::WebviewRegistry;
#[derive(Debug, Serialize, Deserialize)]
struct MessageParameters {
message: String,
}
fn HANDLER(window: &Window, mut req: RpcRequest) -> Option<RpcResponse> {
let mut response = None;
if &req.method == "fullscreen" {
if let Some(params) = req.params.take() {
if let Ok(mut args) = serde_json::from_value::<Vec<bool>>(params) {
if !args.is_empty() {
if args.swap_remove(0) {
window.set_fullscreen(Some(Fullscreen::Borderless(None)));
} else {
window.set_fullscreen(None);
}
};
response = Some(RpcResponse::new_result(req.id.take(), None));
}
}
} else if &req.method == "send-parameters" {
if let Some(params) = req.params.take() {
if let Ok(mut args) = serde_json::from_value::<Vec<MessageParameters>>(params) {
let result = if !args.is_empty() {
let msg = args.swap_remove(0);
Some(Value::String(format!("Hello, {}!", msg.message)))
} else {
// NOTE: in the real-world we should send an error response here!
None
};
// Must always send a response as this is a `call()`
response = Some(RpcResponse::new_result(req.id.take(), result));
}
}
}
response
}