├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── demo.js ├── events.rs ├── jquery.rs └── value.rs ├── index.html └── src ├── bin └── wasm.rs ├── event.rs ├── lib.rs ├── map.rs └── script └── app.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | 13 | # Added by cargo 14 | # 15 | # already existing elements were commented out 16 | 17 | /target 18 | #Cargo.lock 19 | 20 | /dist -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | name = "golde" 4 | version = "0.1.0" 5 | repository = "https://github.com/mrxiaozhuox/golde" 6 | author = ["YuKun Liu "] 7 | description = "Execute Javascript code in the Dioxus, and get the return value." 8 | 9 | edition = "2021" 10 | 11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 12 | 13 | [[bin]] 14 | name = "golde-wasm-demo" 15 | path = "src/bin/wasm.rs" 16 | 17 | # default runtime for desktop 18 | [target.'cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))'.dependencies] 19 | dioxus = { git = "https://github.com/DioxusLabs/dioxus", features = ["desktop"] } 20 | fermi = { git = "https://github.com/dioxuslabs/fermi" } 21 | serde = { version = "1.0.126", features = ['derive'] } 22 | serde_json = "1.0.64" 23 | doson = "0.1.5" 24 | log = "0.4.6" 25 | 26 | # runtime dioxus for wasm 27 | [target.'cfg(any(target_arch = "wasm32", target_arch = "wasm64"))'.dependencies] 28 | dioxus = { git = "https://github.com/DioxusLabs/dioxus", features = ["web"] } 29 | fermi = { git = "https://github.com/dioxuslabs/fermi" } 30 | wasm-bindgen = "0.2.74" 31 | serde = { version = "1.0.126", features = ['derive'] } 32 | serde_json = "1.0.64" 33 | doson = "0.1.5" 34 | log = "0.4.6" 35 | wasm-logger = "0.2.0" 36 | console_error_panic_hook = "0.1.7" 37 | 38 | [patch.crates-io] 39 | dioxus = { git = 'https://github.com/dioxuslabs/dioxus' } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 YuKun Liu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Golde 2 | 3 | [Dioxus](https://github.com/DioxusLabs/dioxus) 4 | 5 | > Execute Javascript code in the Dioxus, and get the return value. 6 | 7 | This demo can help use `Javascript` to calc the `+` operator formula. 8 | 9 | ```rust 10 | use dioxus::prelude::*; 11 | use fermi::*; 12 | use golde::*; 13 | 14 | fn main() { 15 | dioxus::desktop::launch(app) 16 | } 17 | 18 | static RESULT: Atom = |_| 0.0; 19 | 20 | fn app(cx: Scope) -> Element { 21 | 22 | init_app(&cx, |initialized| { 23 | // if you want to use `exec` or `call` in some conditional, you must pass the bool value for *_conditional; 24 | // because in dioxus, any `hook-use` function cannot use in conditional. 25 | exec_conditional(&cx, "console.log(1)".into(), !initialized); 26 | }); 27 | 28 | let a = use_state(&cx, || 0.0); 29 | let b = use_state(&cx, || 0.0); 30 | 31 | let res = use_read(&cx, RESULT); 32 | 33 | let setter = use_set(&cx, RESULT).clone(); 34 | 35 | cx.render(rsx!( 36 | App { 37 | trigger: trigger!( 38 | test => move |_, v| { 39 | setter(v.as_number().unwrap_or(0.0)); 40 | } 41 | ), 42 | input { 43 | value: "{a}", 44 | onchange: move |data| a.set( 45 | data.value.parse::().unwrap_or(0.0) 46 | ) 47 | } 48 | input { 49 | value: "{b}", 50 | onchange: move |data| b.set( 51 | data.value.parse::().unwrap_or(0.0) 52 | ) 53 | } 54 | button { 55 | onclick: move |_| { 56 | let code = format!("{} + {}", &a, &b); 57 | call(&cx, "test", code.to_string()); 58 | }, 59 | "Calc" 60 | } 61 | p { "Result: {res}" } 62 | } 63 | )) 64 | } 65 | ``` 66 | 67 | The `exec` function will not return the result, and `call` function can trigger the callback and get the result. -------------------------------------------------------------------------------- /examples/demo.js: -------------------------------------------------------------------------------- 1 | function value_type(type) { 2 | if (type == "string") { 3 | return "hello world"; 4 | } else if (type == "number") { 5 | return 1; 6 | } else if (type == "boolean") { 7 | return true; 8 | } else if (type == "dict") { 9 | return { 10 | name: "YuKun Liu", 11 | age: 18 12 | }; 13 | } else if (type == "list") { 14 | return [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]; 15 | } 16 | } -------------------------------------------------------------------------------- /examples/events.rs: -------------------------------------------------------------------------------- 1 | use dioxus::prelude::*; 2 | use fermi::*; 3 | use golde::*; 4 | 5 | fn main() { 6 | dioxus::desktop::launch(app) 7 | } 8 | 9 | static RESULT: Atom = |_| 0.0; 10 | 11 | fn app(cx: Scope) -> Element { 12 | init_app(&cx, |_| {}); 13 | 14 | let (a, a_setter) = use_state(&cx, || 0.0); 15 | let (b, b_setter) = use_state(&cx, || 0.0); 16 | 17 | let res = use_read(&cx, RESULT); 18 | 19 | let setter = use_set(&cx, RESULT).clone(); 20 | 21 | cx.render(rsx!( 22 | App { 23 | trigger: trigger!( 24 | test => move |_, v| { 25 | setter(v.as_number().unwrap_or(0.0)); 26 | } 27 | ), 28 | input { 29 | value: "{a}", 30 | onchange: move |data| a_setter( 31 | data.value.parse::().unwrap_or(0.0) 32 | ) 33 | } 34 | input { 35 | value: "{b}", 36 | onchange: move |data| b_setter( 37 | data.value.parse::().unwrap_or(0.0) 38 | ) 39 | } 40 | button { 41 | onclick: move |_| { 42 | let code = format!("{} + {}", &a, &b); 43 | println!("{:?}", code); 44 | call(&cx, "test", code); 45 | }, 46 | "Calc" 47 | } 48 | p { "Result: {res}" } 49 | } 50 | )) 51 | } 52 | -------------------------------------------------------------------------------- /examples/jquery.rs: -------------------------------------------------------------------------------- 1 | use dioxus::prelude::*; 2 | use golde::*; 3 | 4 | fn main() { 5 | dioxus::desktop::launch(app) 6 | } 7 | 8 | fn app(cx: Scope) -> Element { 9 | init_app(&cx, |_| {}); 10 | 11 | cx.render(rsx!( 12 | App { 13 | trigger: trigger!( 14 | jquery_test => |_, v| { 15 | println!("{:?}", v); 16 | } 17 | ), 18 | button { 19 | onclick: move |_| { 20 | call(&cx, "jquery_test", " 21 | if ($('#hello').text() == 'Hello World!') { 22 | $('#hello').text('Hello Dioxus!'); 23 | } else { 24 | $('#hello').text('Hello World!'); 25 | } 26 | ".to_string()); 27 | }, 28 | "Change Value From JQuery" 29 | } 30 | p { 31 | id: "hello", 32 | "Hello World!" 33 | } 34 | script { src: "https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js" } 35 | } 36 | )) 37 | } 38 | -------------------------------------------------------------------------------- /examples/value.rs: -------------------------------------------------------------------------------- 1 | use dioxus::prelude::*; 2 | use golde::*; 3 | 4 | fn main() { 5 | dioxus::desktop::launch(app) 6 | } 7 | 8 | fn app(cx: Scope) -> Element { 9 | init_app(&cx, |_| {}); 10 | 11 | cx.render(rsx!( 12 | App { 13 | trigger: trigger!( 14 | val_dict => |_, v| { 15 | println!("{:?}", v); 16 | }, 17 | val_list => |_, v| { 18 | println!("{:?}", v); 19 | } 20 | ), 21 | button { 22 | onclick: move | _ | { 23 | call( & cx, "val_dict", "value_type('dict')".to_string()); 24 | }, 25 | "Javascript::Object => Rust::Dict" 26 | } 27 | button { 28 | onclick: move | _ | { 29 | call( & cx, "val_list", "value_type('list')".to_string()); 30 | }, 31 | "Javascript::Array => Rust::List" 32 | } 33 | script { [include_str!("./demo.js")] } 34 | } 35 | )) 36 | } 37 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /src/bin/wasm.rs: -------------------------------------------------------------------------------- 1 | fn main() { } -------------------------------------------------------------------------------- /src/event.rs: -------------------------------------------------------------------------------- 1 | use doson::DataValue; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | #[derive(Debug, Clone, Serialize, Deserialize)] 5 | pub struct Event { 6 | pub code: String, 7 | pub result: DataValue, 8 | } 9 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case)] 2 | 3 | pub mod event; 4 | pub mod map; 5 | 6 | use std::collections::HashMap; 7 | 8 | use dioxus::prelude::*; 9 | use doson::DataValue; 10 | use fermi::{use_read, use_set, Atom}; 11 | // use once_cell::unsync::Lazy; 12 | 13 | pub type Value = DataValue; 14 | pub type Trigger = HashMap>; 15 | 16 | #[macro_export] 17 | macro_rules! trigger { 18 | ($( $key: ident => $val: expr ),*) => {{ 19 | let mut map: Trigger = std::collections::HashMap::new(); 20 | $( map.insert(String::from(stringify!($key)), std::boxed::Box::new($val)); )* 21 | map 22 | }} 23 | } 24 | 25 | pub fn use_once(cx: &ScopeState, f: impl FnOnce()) { 26 | let init = cx.use_hook(|_| true); 27 | if *init { 28 | f(); 29 | *init = false; 30 | } 31 | } 32 | 33 | #[derive(Props)] 34 | pub struct AppProps<'a> { 35 | children: Element<'a>, 36 | trigger: Trigger, 37 | } 38 | 39 | static GOLDE_EVENT_QUEUE: Atom> = |_| map::Map::new(); 40 | 41 | pub fn init_app(cx: &Scope, f: impl Fn(bool)) { 42 | let init = cx.use_hook(|_| false); 43 | f(*init); 44 | *init = true; 45 | } 46 | 47 | pub fn call(cx: &ScopeState, name: &str, code: String) { 48 | let mut golde_event_queue = use_read(cx, GOLDE_EVENT_QUEUE).clone(); 49 | golde_event_queue.set( 50 | name.to_string(), 51 | event::Event { 52 | code, 53 | result: DataValue::None, 54 | }, 55 | ); 56 | 57 | let setter = use_set(cx, GOLDE_EVENT_QUEUE); 58 | setter(golde_event_queue.clone()); 59 | } 60 | 61 | pub fn call_conditional(cx: &ScopeState, name: &str, code: String, state: bool) { 62 | let mut golde_event_queue = use_read(cx, GOLDE_EVENT_QUEUE).clone(); 63 | golde_event_queue.set( 64 | name.to_string(), 65 | event::Event { 66 | code, 67 | result: DataValue::None, 68 | }, 69 | ); 70 | 71 | let setter = use_set(cx, GOLDE_EVENT_QUEUE); 72 | if state { 73 | setter(golde_event_queue.clone()); 74 | } 75 | } 76 | 77 | pub fn exec(cx: &ScopeState, code: String) { 78 | let mut golde_event_queue = use_read(cx, GOLDE_EVENT_QUEUE).clone(); 79 | golde_event_queue.set( 80 | "_JUST_CALL_".to_string(), 81 | event::Event { 82 | code, 83 | result: DataValue::String("".to_string()), 84 | }, 85 | ); 86 | let setter = use_set(cx, GOLDE_EVENT_QUEUE); 87 | setter(golde_event_queue.clone()); 88 | } 89 | 90 | pub fn exec_conditional(cx: &ScopeState, code: String, state: bool) { 91 | let mut golde_event_queue = use_read(cx, GOLDE_EVENT_QUEUE).clone(); 92 | golde_event_queue.set( 93 | "_JUST_CALL_".to_string(), 94 | event::Event { 95 | code, 96 | result: DataValue::String("".to_string()), 97 | }, 98 | ); 99 | let setter = use_set(cx, GOLDE_EVENT_QUEUE); 100 | 101 | if state { 102 | setter(golde_event_queue.clone()); 103 | } 104 | } 105 | 106 | pub fn App<'a>(cx: Scope<'a, AppProps<'a>>) -> Element { 107 | // check the runtime platform, now the `golde` just support WASM and Desktop 108 | let wasm_runtime = cfg!(any(target_arch = "wasm32", target_arch = "wasm64")); 109 | 110 | let platform = (if wasm_runtime { "WASM" } else { "Desktop" }).to_string(); 111 | 112 | let (initialized, initialized_setter) = use_state(&cx, || false); 113 | if !initialized { 114 | log::info!("Dioxus [Golde] Runtime Platform: {}", platform); 115 | initialized_setter(true); 116 | } 117 | 118 | let golde_event_queue = use_read(&cx, GOLDE_EVENT_QUEUE); 119 | let setter = use_set(&cx, GOLDE_EVENT_QUEUE); 120 | 121 | if golde_event_queue.len() > 0 { 122 | // here will call the callback function and return the result. 123 | let mut new_event_queue: map::Map = golde_event_queue.clone(); 124 | let mut need_reload_queue: bool = false; 125 | 126 | for (name, data) in &golde_event_queue.inner { 127 | if data.result != DataValue::None && data.result != DataValue::String("".into()) { 128 | let callback = cx.props.trigger.get(name); 129 | if let Some(fun) = callback { 130 | fun(data.code.clone(), data.result.clone()); 131 | } 132 | need_reload_queue = true; 133 | new_event_queue.inner.remove(name); 134 | } 135 | } 136 | if need_reload_queue { 137 | setter(new_event_queue); 138 | } 139 | } 140 | 141 | 142 | cx.render(rsx!( 143 | div { 144 | id: "GoldeAppStatus", 145 | style: "display: none;", 146 | "platform": "{platform}", 147 | form { 148 | id: "GoldeEventQueue", 149 | "value": "{golde_event_queue}", 150 | onsubmit: move |data| { 151 | 152 | let queue; 153 | if !wasm_runtime { 154 | queue = map::Map { 155 | inner: serde_json::from_str:: 156 | > 157 | (&data.value).unwrap() 158 | }; 159 | } else { 160 | let r = WebAssemblyGetResult(); 161 | queue = map::Map { 162 | inner: serde_json::from_str:: 163 | > 164 | (&r).unwrap() 165 | }; 166 | // log::info!("NEW_: {}", queue); 167 | } 168 | 169 | let setter = use_set(&cx, GOLDE_EVENT_QUEUE); 170 | setter(queue); 171 | }, 172 | button { 173 | id: "GoldeEventQueueSubmit", 174 | r#type: "submit", 175 | } 176 | } 177 | } 178 | &cx.props.children, 179 | script { "var platform = \"{platform}\";" } 180 | script { [include_str!("./script/app.js")] } 181 | )) 182 | } 183 | 184 | #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] 185 | #[wasm_bindgen::prelude::wasm_bindgen] 186 | extern "C" { 187 | fn WebAssemblyGetResult() -> String; 188 | } 189 | 190 | #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] 191 | fn WebAssemblyGetResult() -> String { 192 | String::new() 193 | } -------------------------------------------------------------------------------- /src/map.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | fmt::{Debug, Display}, 4 | hash::Hash, 5 | }; 6 | 7 | #[derive(Clone, Debug, Default)] 8 | pub struct Map { 9 | pub(crate) inner: HashMap, 10 | } 11 | 12 | impl Map { 13 | pub fn new() -> Map { 14 | Map { 15 | inner: HashMap::new(), 16 | } 17 | } 18 | 19 | pub fn set(&mut self, k: K, v: V) -> Option { 20 | self.inner.insert(k, v) 21 | } 22 | 23 | pub fn get(&self, k: K) -> Option<&V> { 24 | self.inner.get(&k) 25 | } 26 | 27 | pub fn len(&self) -> usize { 28 | self.inner.len() 29 | } 30 | } 31 | 32 | impl Display for Map 33 | where 34 | K: Debug + std::cmp::Eq + std::hash::Hash + serde::ser::Serialize, 35 | V: Debug + serde::ser::Serialize, 36 | { 37 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 38 | write!( 39 | f, 40 | "{}", 41 | serde_json::to_string(&self.inner).unwrap_or_else(|_| "{}".to_string()) 42 | ) 43 | } 44 | } 45 | 46 | #[test] 47 | fn test_map_display() { 48 | let map: Map<&str, doson::DataValue> = Map::new(); 49 | println!("{}", map); 50 | } 51 | -------------------------------------------------------------------------------- /src/script/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Golde 3 | * YuKun Liu 4 | * https://github.com/mrxiaozhuox/golde/ 5 | */ 6 | 7 | setInterval(function() { 8 | 9 | if (platform == "WASM") { 10 | var queue_str = document.getElementById("GoldeEventQueue").getAttribute("value"); 11 | } else { 12 | var queue_str = document.getElementById("GoldeEventQueue").value; 13 | } 14 | 15 | try { 16 | var queue = JSON.parse(queue_str); 17 | } catch { 18 | var queue = {}; 19 | } 20 | 21 | var new_queue = {}; 22 | var need_submit = false; 23 | 24 | for (const key in queue) { 25 | var data = queue[key]; 26 | 27 | // console.log(queue[key]); 28 | 29 | if (data.result == "None") { 30 | need_submit = true; 31 | try { 32 | var result = eval(data.code); 33 | new_queue[key] = { 34 | code: data.code, 35 | result: dataValueParser(result), 36 | }; 37 | } catch { 38 | delete new_queue[key]; 39 | } 40 | } else if (data.result["String"] === "") { 41 | need_submit = true; 42 | try { 43 | eval(data.code); 44 | } catch { 45 | // error 46 | } 47 | } 48 | } 49 | 50 | if (need_submit) { 51 | if (platform == "WASM") { 52 | document.getElementById("GoldeEventQueue").setAttribute("value", JSON.stringify(new_queue)); 53 | } else { 54 | document.getElementById("GoldeEventQueue").value = JSON.stringify(new_queue); 55 | } 56 | 57 | document.getElementById("GoldeEventQueueSubmit").click(); 58 | } 59 | 60 | 61 | }, 50); 62 | 63 | document.getElementById("GoldeEventQueue").onsubmit = function() { 64 | return false; 65 | } 66 | 67 | function dataValueParser(value) { 68 | 69 | if (typeof value == "boolean") { 70 | return { "Boolesn": value }; 71 | } 72 | if (typeof value == "number") { 73 | return { "Number": value }; 74 | } 75 | if (typeof value == "string") { 76 | return { "String": value }; 77 | } 78 | if (typeof value == "object") { 79 | 80 | if (Array.isArray(value)) { 81 | 82 | var temp = []; 83 | for (const key in value) { 84 | temp.push(dataValueParser(value[key])); 85 | } 86 | return { "List": temp }; 87 | 88 | } else { 89 | 90 | if (value === null) { 91 | return { "String": "" }; 92 | } 93 | 94 | var temp = {}; 95 | Object.keys(value).map(key => { 96 | temp[key] = dataValueParser(value[key]); 97 | }) 98 | return { "Dict": temp }; 99 | 100 | } 101 | } 102 | 103 | if (typeof value == "undefined") { 104 | return { "String": "" }; 105 | } 106 | if (typeof value == "function") { 107 | return { "String": "" }; 108 | } 109 | if (typeof value == "symbol") { 110 | return { "String": "" }; 111 | } 112 | return { "String": "" }; 113 | } 114 | 115 | function WebAssemblyGetResult() { 116 | return document.getElementById("GoldeEventQueue").getAttribute("value"); 117 | } 118 | --------------------------------------------------------------------------------