From 2cd7f76860b95bdf242182591f8e13c918b220a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pokrywka?= Date: Sun, 24 Mar 2024 20:58:15 +0100 Subject: [PATCH] Added window! and document! macros, refactored JsValue typing --- CHANGES.md | 1 + Makefile.toml | 7 +- crates/vertigo-macro/src/api_access.rs | 67 ++++++ crates/vertigo-macro/src/bind.rs | 16 +- crates/vertigo-macro/src/lib.rs | 14 ++ .../src/driver_module/api/api_dom_access.rs | 1 - .../src/driver_module/api/api_import.rs | 18 +- .../driver_module/js_value/js_json_struct.rs | 30 +-- .../driver_module/js_value/js_value_struct.rs | 195 ++++++++++++----- .../src/driver_module/js_value/tests.rs | 15 ++ .../driver_module/src_js/api_browser/fetch.ts | 13 +- .../vertigo/src/driver_module/src_js/guard.ts | 10 +- .../src/driver_module/src_js/jsjson.ts | 37 ++-- .../src/driver_module/src_js/jsvalue.ts | 174 +++++++++------ crates/vertigo/src/driver_module/wasm_run.js | 206 +++++++++++------- crates/vertigo/src/lib.rs | 36 +++ crates/vertigo/src/tests/api_access/mod.rs | 23 ++ crates/vertigo/src/tests/mod.rs | 1 + demo/app/src/app/js_api_access/mod.rs | 60 +++++ demo/app/src/app/mod.rs | 1 + demo/app/src/app/render.rs | 11 +- demo/app/src/app/route.rs | 4 + demo/app/src/app/sudoku/component/mod.rs | 2 +- .../sudoku/component/render_cell_possible.rs | 6 +- .../app/sudoku/component/render_cell_value.rs | 2 +- demo/app/src/app/sudoku/state/mod.rs | 8 +- demo/app/src/app/sudoku/state/number_item.rs | 54 +++-- .../src/app/sudoku/state/possible_values.rs | 6 +- .../app/sudoku/state/possible_values_last.rs | 2 +- tests/basic/tests.rs | 31 ++- 30 files changed, 733 insertions(+), 318 deletions(-) create mode 100644 crates/vertigo-macro/src/api_access.rs create mode 100644 crates/vertigo/src/tests/api_access/mod.rs create mode 100644 demo/app/src/app/js_api_access/mod.rs diff --git a/CHANGES.md b/CHANGES.md index 76f154dc..a3556b1b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,7 @@ ### Added +* `window!` and `document!` macro to allow invoking simple JavaScript commands * `Driver::plains` method to allow responding with plaintext pages * In `css!` macro there is now possibility to reference a class created by another `css!` using `[]` brackets * Enums nad newtypes support in `AutoJsJson` diff --git a/Makefile.toml b/Makefile.toml index 594db682..c20204ea 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -72,7 +72,8 @@ script = [ # JavaScript dev builds -[tasks.build-js-run-ts] +[tasks.internal-run-ts] +private = true command = "npx" args = [ "-p", "typescript", "tsc", "--strict", @@ -86,10 +87,8 @@ args = [ "-p", "typescript", "tsc", ] [tasks.build-js] -dependencies = [ "build-js-run-ts" ] +dependencies = [ "internal-run-ts" ] script = [ - "rm -Rf crates/vertigo-cli/src/serve/js_value", - "cp -R crates/vertigo/src/driver_module/js_value crates/vertigo-cli/src/serve/js_value", "npx rollup crates/vertigo/src/driver_module/src_js_build/index.js --file crates/vertigo/src/driver_module/wasm_run.js", "rm -rf crates/vertigo/src/driver_module/src_js_build", ] diff --git a/crates/vertigo-macro/src/api_access.rs b/crates/vertigo-macro/src/api_access.rs new file mode 100644 index 00000000..53f33272 --- /dev/null +++ b/crates/vertigo-macro/src/api_access.rs @@ -0,0 +1,67 @@ +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use syn::{spanned::Spanned, Expr, Lit}; + +pub(crate) fn api_access_inner(root: &str, input: TokenStream) -> TokenStream { + let input: TokenStream2 = input.into(); + + use syn::parse::Parser; + let data = syn::punctuated::Punctuated::::parse_terminated + .parse2(input) + .unwrap(); + + let mut param_iter = data.into_iter(); + let first_param = param_iter.next().unwrap(); + let first_param_span = first_param.span(); + + let inner = match first_param { + Expr::Lit(expr_lit) => { + match expr_lit.lit { + Lit::Str(param_lit) => { + let param_repr = param_lit.token().to_string(); + let param_str = param_repr.trim_matches('"'); + if let Some(func_name) = param_str.strip_suffix("()") { + // Function call + let mut args = vec![]; + + for arg in param_iter { + args.push(quote! { vertigo::JsValue::from(#arg) }) + } + + quote! { .call(#func_name, vec![ #(#args,)* ]) } + } else { + // Property get + if let Some(arg) = param_iter.next() { + diagnostic!( + arg.span(), + proc_macro_error::Level::Error, + "Properties don't accept arguments, missing () in func name?" + ) + .span_suggestion(first_param_span, "hint", format!("Try `{}()`", param_str)) + .emit() + } + + quote! { .get(#param_str) } + } + } + _ => { + emit_error!(expr_lit.span(), "Expected literal string as first parameter (property or function name) (1)"); + quote! {} + } + } + } + _ => { + emit_error!(first_param_span, "Expected literal string as first parameter (property or function name) (2)"); + quote! {} + } + }; + + quote! { + vertigo::get_driver() + .dom_access() + .root(#root) + #inner + .fetch() + }.into() +} diff --git a/crates/vertigo-macro/src/bind.rs b/crates/vertigo-macro/src/bind.rs index 0ad898d2..4d946b12 100644 --- a/crates/vertigo-macro/src/bind.rs +++ b/crates/vertigo-macro/src/bind.rs @@ -1,13 +1,15 @@ use std::collections::HashSet; use std::collections::VecDeque; -use proc_macro::{Span, TokenStream, TokenTree}; -use proc_macro2::Ident as Ident2; +use proc_macro::{Span, TokenStream}; +use proc_macro2::Ident; use proc_macro2::TokenStream as TokenStream2; +use proc_macro2::TokenTree; use quote::quote; use syn::__private::quote::format_ident; pub(crate) fn bind_inner(input: TokenStream) -> Result { + let input: TokenStream2 = input.into(); let tokens = input.into_iter().collect::>(); let TokensParamsBody { @@ -18,7 +20,7 @@ pub(crate) fn bind_inner(input: TokenStream) -> Result { let mut clone_stm = Vec::::new(); let first_pipe = is_first_pipe_char(body.as_slice()); - let body: TokenStream2 = body.iter().cloned().collect::().into(); + let body = body.iter().cloned().collect::(); let mut idents_seen = HashSet::new(); @@ -31,7 +33,7 @@ pub(crate) fn bind_inner(input: TokenStream) -> Result { emit_error!(item.last().unwrap().span(), "Conflicting variable name: {}", param_name); } - let item_expr: TokenStream2 = item.iter().cloned().collect::().into(); + let item_expr = item.iter().cloned().collect::(); clone_stm.push(quote! { let #param_name = #item_expr.clone(); @@ -60,6 +62,7 @@ pub(crate) fn bind_inner(input: TokenStream) -> Result { } pub(crate) fn bind_spawn_inner(input: TokenStream) -> Result { + let input: TokenStream2 = input.into(); let tokens = input.into_iter().collect::>(); let TokensParamsBody { @@ -86,6 +89,7 @@ pub(crate) fn bind_spawn_inner(input: TokenStream) -> Result Result { + let input: TokenStream2 = input.into(); let tokens = input.into_iter().collect::>(); let TokensParamsBody { @@ -134,7 +138,7 @@ fn is_char(token: &TokenTree, char: char) -> bool { } } -fn find_param_name(params: &[TokenTree]) -> Option { +fn find_param_name(params: &[TokenTree]) -> Option { if let Some(last) = params.last() { if let TokenTree::Ident(value) = &last { Some(format_ident!("{}", value.to_string())) @@ -255,7 +259,7 @@ fn split_params_and_body(tokens: &[TokenTree]) -> Result TokenStream2 { - tokens.iter().cloned().collect::().into() + tokens.iter().cloned().collect::() } fn get_type(tokens: &[TokenTree]) -> Result<&[TokenTree], String> { diff --git a/crates/vertigo-macro/src/lib.rs b/crates/vertigo-macro/src/lib.rs index 58e953d3..f02ac7b9 100644 --- a/crates/vertigo-macro/src/lib.rs +++ b/crates/vertigo-macro/src/lib.rs @@ -5,6 +5,7 @@ extern crate pest_derive; #[macro_use] extern crate proc_macro_error; +mod api_access; mod bind; mod component; mod css_parser; @@ -18,6 +19,7 @@ use proc_macro::{Span, TokenStream}; use quote::quote; use crate::{ + api_access::api_access_inner, bind::{bind_inner, bind_rc_inner, bind_spawn_inner}, component::component_inner, css_parser::generate_css_string, @@ -125,6 +127,18 @@ pub fn component(_attr: TokenStream, input: TokenStream) -> TokenStream { component_inner(input) } +#[proc_macro] +#[proc_macro_error] +pub fn window(input: TokenStream) -> TokenStream { + api_access_inner("window", input) +} + +#[proc_macro] +#[proc_macro_error] +pub fn document(input: TokenStream) -> TokenStream { + api_access_inner("document", input) +} + fn convert_to_tokens(input: Result) -> TokenStream { match input { Ok(body) => body, diff --git a/crates/vertigo/src/driver_module/api/api_dom_access.rs b/crates/vertigo/src/driver_module/api/api_dom_access.rs index d893b89a..84025097 100644 --- a/crates/vertigo/src/driver_module/api/api_dom_access.rs +++ b/crates/vertigo/src/driver_module/api/api_dom_access.rs @@ -111,7 +111,6 @@ impl DomAccess { } } - #[must_use] pub fn fetch(self) -> JsValue { let memory = JsValue::List(self.builder).to_snapshot(); let (ptr, size) = memory.get_ptr_and_size(); diff --git a/crates/vertigo/src/driver_module/api/api_import.rs b/crates/vertigo/src/driver_module/api/api_import.rs index 5cfb0acc..52f68c9f 100644 --- a/crates/vertigo/src/driver_module/api/api_import.rs +++ b/crates/vertigo/src/driver_module/api/api_import.rs @@ -264,12 +264,18 @@ impl ApiImport { .call("now", vec![]) .fetch(); - if let JsValue::I64(time) = result { - time as u64 as InstantType - } else { - self.panic_message - .show(format!("api.instant_now -> incorrect result {result:?}")); - 0_u64 + match result { + JsValue::I64(time) => { + time as u64 as InstantType + } + JsValue::F64(time) => { + time as u64 as InstantType + } + _ => { + self.panic_message + .show(format!("api.instant_now -> incorrect result {result:?}")); + 0_u64 + } } } diff --git a/crates/vertigo/src/driver_module/js_value/js_json_struct.rs b/crates/vertigo/src/driver_module/js_value/js_json_struct.rs index f86132d1..745e6435 100644 --- a/crates/vertigo/src/driver_module/js_value/js_json_struct.rs +++ b/crates/vertigo/src/driver_module/js_value/js_json_struct.rs @@ -10,14 +10,14 @@ const LIST_COUNT: u32 = 4; const OBJECT_COUNT: u32 = 2; enum JsJsonConst { - True, - False, - Null, - - String, - Number, - List, - Object, + True = 1, + False = 2, + Null = 3, + + String = 4, + Number = 5, + List = 6, + Object = 7, } impl JsJsonConst { @@ -37,15 +37,7 @@ impl JsJsonConst { impl From for u8 { fn from(value: JsJsonConst) -> Self { - match value { - JsJsonConst::True => 1, - JsJsonConst::False => 2, - JsJsonConst::Null => 3, - JsJsonConst::String => 4, - JsJsonConst::Number => 5, - JsJsonConst::List => 6, - JsJsonConst::Object => 7, - } + value as u8 } } @@ -54,7 +46,9 @@ impl From for u8 { /* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number#number_encoding - The JavaScript Number type is a double-precision 64-bit binary format IEEE 754 value, like double in Java or C#. This means it can represent fractional values, but there are some limits to the stored number's magnitude and precision. Very briefly, an IEEE 754 double-precision number uses 64 bits to represent 3 parts: + The JavaScript Number type is a double-precision 64-bit binary format IEEE 754 value, like double in Java or C#. + This means it can represent fractional values, but there are some limits to the stored number's magnitude + and precision. Very briefly, an IEEE 754 double-precision number uses 64 bits to represent 3 parts: 1 bit for the sign (positive or negative) 11 bits for the exponent (-1022 to 1023) diff --git a/crates/vertigo/src/driver_module/js_value/js_value_struct.rs b/crates/vertigo/src/driver_module/js_value/js_value_struct.rs index b8b346d3..a9af77e8 100644 --- a/crates/vertigo/src/driver_module/js_value/js_value_struct.rs +++ b/crates/vertigo/src/driver_module/js_value/js_value_struct.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use super::{ js_json_struct::{decode_js_json_inner, JsJson}, js_value_list_decoder::JsValueListDecoder, @@ -5,7 +7,6 @@ use super::{ memory_block_read::MemoryBlockRead, memory_block_write::MemoryBlockWrite, }; -use std::collections::HashMap; const PARAM_TYPE: u32 = 1; const STRING_SIZE: u32 = 4; @@ -14,21 +15,22 @@ const LIST_COUNT: u32 = 4; const OBJECT_COUNT: u32 = 2; enum JsValueConst { - U32, - I32, - U64, - I64, - - True, - False, - Null, - Undefined, - - Vec, - String, - List, - Object, - Json, + U32 = 1, + I32 = 2, + U64 = 3, + I64 = 4, + F64 = 5, + + True = 6, + False = 7, + Null = 8, + Undefined = 9, + + Vec = 10, + String = 11, + List = 12, + Object = 13, + Json = 14, } impl JsValueConst { @@ -38,15 +40,16 @@ impl JsValueConst { 2 => Some(JsValueConst::I32), 3 => Some(JsValueConst::U64), 4 => Some(JsValueConst::I64), - 5 => Some(JsValueConst::True), - 6 => Some(JsValueConst::False), - 7 => Some(JsValueConst::Null), - 8 => Some(JsValueConst::Undefined), - 9 => Some(JsValueConst::Vec), - 10 => Some(JsValueConst::String), - 11 => Some(JsValueConst::List), - 12 => Some(JsValueConst::Object), - 13 => Some(JsValueConst::Json), + 5 => Some(JsValueConst::F64), + 6 => Some(JsValueConst::True), + 7 => Some(JsValueConst::False), + 8 => Some(JsValueConst::Null), + 9 => Some(JsValueConst::Undefined), + 10 => Some(JsValueConst::Vec), + 11 => Some(JsValueConst::String), + 12 => Some(JsValueConst::List), + 13 => Some(JsValueConst::Object), + 14 => Some(JsValueConst::Json), _ => None, } } @@ -54,23 +57,7 @@ impl JsValueConst { impl From for u8 { fn from(value: JsValueConst) -> Self { - match value { - JsValueConst::U32 => 1, - JsValueConst::I32 => 2, - JsValueConst::U64 => 3, - JsValueConst::I64 => 4, - - JsValueConst::True => 5, - JsValueConst::False => 6, - JsValueConst::Null => 7, - JsValueConst::Undefined => 8, - - JsValueConst::Vec => 9, - JsValueConst::String => 10, - JsValueConst::List => 11, - JsValueConst::Object => 12, - JsValueConst::Json => 13, - } + value as u8 } } @@ -81,15 +68,16 @@ pub enum JsValue { I32(i32), U64(u64), I64(i64), + F64(f64), True, False, Null, Undefined, - Vec(Vec), //type, length of the sequence of bytes, sequence of bytes - String(String), //type, length of the sequence of bytes, sequence of bytes - List(Vec), //type, length + Vec(Vec), // type, length, sequence of bytes + String(String), // type, length, sequence of chars + List(Vec), // type, length Object(HashMap), Json(JsJson), @@ -126,10 +114,11 @@ impl JsValue { fn get_size(&self) -> u32 { match self { - Self::U32(..) => PARAM_TYPE + 4, - Self::I32(..) => PARAM_TYPE + 4, - Self::U64(..) => PARAM_TYPE + 8, - Self::I64(..) => PARAM_TYPE + 8, + Self::U32(_) => PARAM_TYPE + 4, + Self::I32(_) => PARAM_TYPE + 4, + Self::U64(_) => PARAM_TYPE + 8, + Self::I64(_) => PARAM_TYPE + 8, + Self::F64(_) => PARAM_TYPE + 8, Self::True => PARAM_TYPE, Self::False => PARAM_TYPE, @@ -179,6 +168,10 @@ impl JsValue { buff.write_u8(JsValueConst::I64); buff.write_i64(*value); } + Self::F64(value)=> { + buff.write_u8(JsValueConst::F64); + buff.write_f64(*value); + } Self::True => { buff.write_u8(JsValueConst::True); @@ -238,19 +231,20 @@ impl JsValue { pub fn typename(&self) -> &'static str { match self { - Self::U32(..) => "u32", - Self::I32(..) => "i32", - Self::U64(..) => "u64", - Self::I64(..) => "i64", + Self::U32(_) => "u32", + Self::I32(_) => "i32", + Self::U64(_) => "u64", + Self::I64(_) => "i64", + Self::F64(_) => "f64", Self::True => "true", Self::False => "false", Self::Null => "null", Self::Undefined => "undefined", - Self::Vec(..) => "vec", - Self::String(..) => "string", - Self::List(..) => "list", - Self::Object(..) => "object", - Self::Json(..) => "json", + Self::Vec(_) => "vec", + Self::String(_) => "string", + Self::List(_) => "list", + Self::Object(_) => "object", + Self::Json(_) => "json", } } @@ -304,6 +298,10 @@ fn decode_js_value_inner(buffer: &mut MemoryBlockRead) -> Result { + let value = buffer.get_f64(); + JsValue::F64(value) + } JsValueConst::True => JsValue::True, JsValueConst::False => JsValue::False, JsValueConst::Null => JsValue::Null, @@ -355,3 +353,84 @@ fn decode_js_value_inner(buffer: &mut MemoryBlockRead) -> Result { + impl From<$from> for JsValue { + fn from(value: $from) -> Self { + Self::$js_type(value as $to) + } + } + }; +} + +impl_from!(i8, i32, I32); +impl_from!(i16, i32, I32); +impl_from!(i32, i32, I32); +impl_from!(i64, i64, I64); +impl_from!(isize, i64, I64); + +impl_from!(u8, u32, U32); +impl_from!(u16, u32, U32); +impl_from!(u32, u32, U32); +impl_from!(u64, u64, U64); +impl_from!(usize, u64, U64); + +impl_from!(f64, f64, F64); + +impl From<&str> for JsValue { + fn from(value: &str) -> Self { + Self::str(value) + } +} + +impl From for JsValue { + fn from(value: String) -> Self { + Self::String(value) + } +} + +impl From for JsValue { + fn from(value: bool) -> Self { + if value { + Self::True + } else { + Self::False + } + } +} + +impl From> for JsValue { + fn from(value: HashMap) -> Self { + Self::Object(value) + } +} + +impl FromIterator<(String, JsValue)> for JsValue { + fn from_iter>(iter: T) -> Self { + let hash_map = HashMap::from_iter(iter); + JsValue::Object(hash_map) + } +} + +impl From> for JsValue { + fn from(value: Vec<(String, JsValue)>) -> Self { + JsValue::from_iter(value) + } +} + +impl<'a> FromIterator<(&'a str, JsValue)> for JsValue { + fn from_iter>(iter: T) -> Self { + let iter = iter.into_iter() + .map(|(k, v)| (k.to_string(), v)); + let hash_map = HashMap::from_iter(iter); + JsValue::Object(hash_map) + } +} + +impl From> for JsValue { + fn from(value: Vec<(&str, JsValue)>) -> Self { + JsValue::from_iter(value) + } +} + diff --git a/crates/vertigo/src/driver_module/js_value/tests.rs b/crates/vertigo/src/driver_module/js_value/tests.rs index 003cbb8f..cc8c7a85 100644 --- a/crates/vertigo/src/driver_module/js_value/tests.rs +++ b/crates/vertigo/src/driver_module/js_value/tests.rs @@ -16,6 +16,21 @@ fn json_json_string() { assert_eq!(data1, data2); } +#[test] +fn json_json_float() { + let data1 = JsValue::F64(3.15); + + let block = data1.to_snapshot(); + + let Ok(data2) = JsValue::from_block(block) else { + unreachable!(); + }; + + assert_eq!(data2, data1); + + assert_eq!(data2, JsValue::F64(3.15)); +} + #[test] fn json_json_list() { let data1 = JsValue::Json(JsJson::List(vec![ diff --git a/crates/vertigo/src/driver_module/src_js/api_browser/fetch.ts b/crates/vertigo/src/driver_module/src_js/api_browser/fetch.ts index faa6c04d..b0b7cea6 100644 --- a/crates/vertigo/src/driver_module/src_js/api_browser/fetch.ts +++ b/crates/vertigo/src/driver_module/src_js/api_browser/fetch.ts @@ -1,4 +1,5 @@ import { JsJsonType } from "../jsjson"; +import { JsValueConst } from "../jsvalue"; import { ModuleControllerType } from "../wasm_init"; import { ExportType } from "../wasm_module"; @@ -43,7 +44,7 @@ const catchError = async ( wasm.wasm_callback(callback_id, [ false, //ok - { type: 'u32', value: response.status }, //http code + { type: JsValueConst.U32, value: response.status }, //http code responseMessage //body (string) ]); } @@ -137,9 +138,9 @@ export class Fetch { wasm.wasm_callback(callback_id, [ true, //ok - { type: 'u32', value: response.status }, //http code + { type: JsValueConst.U32, value: response.status }, //http code { //body (json) - type: 'json', + type: JsValueConst.Json, value: json } ]); @@ -153,7 +154,7 @@ export class Fetch { wasm.wasm_callback(callback_id, [ true, //ok - { type: 'u32', value: response.status }, //http code + { type: JsValueConst.U32, value: response.status }, //http code text //body (text) ]); }); @@ -166,7 +167,7 @@ export class Fetch { wasm.wasm_callback(callback_id, [ true, //ok - { type: 'u32', value: response.status }, //http code + { type: JsValueConst.U32, value: response.status }, //http code textUint8Array //body (text) ]); }); @@ -176,7 +177,7 @@ export class Fetch { wasm.wasm_callback(callback_id, [ false, //ok - { type: 'u32', value: 0 }, //http code + { type: JsValueConst.U32, value: 0 }, //http code responseMessage //body (string) ]); } diff --git a/crates/vertigo/src/driver_module/src_js/guard.ts b/crates/vertigo/src/driver_module/src_js/guard.ts index 05ae0774..c32a81d8 100644 --- a/crates/vertigo/src/driver_module/src_js/guard.ts +++ b/crates/vertigo/src/driver_module/src_js/guard.ts @@ -1,4 +1,4 @@ -import { JsValueType } from "./jsvalue"; +import { JsValueType, JsValueConst } from "./jsvalue"; export namespace GuardJsValue { export const isString = (value: JsValueType): value is string => { @@ -9,17 +9,17 @@ export namespace GuardJsValue { return value === null || typeof value === 'string'; } - export const isNumber = (value: JsValueType): value is { type: 'u32', value: number } | { type: 'i32', value: number } => { + export const isNumber = (value: JsValueType): value is { type: typeof JsValueConst.U32, value: number } | { type: typeof JsValueConst.I32, value: number } => { if (typeof value === 'object' && value !== null && 'type' in value) { - return value.type === 'i32' || value.type === 'u32' + return value.type === JsValueConst.I32 || value.type === JsValueConst.U32 } return false; } - export const isBigInt = (value: JsValueType): value is { type: 'u64', value: bigint } | { type: 'i64', value: bigint } => { + export const isBigInt = (value: JsValueType): value is { type: typeof JsValueConst.U64, value: bigint } | { type: typeof JsValueConst.I64, value: bigint } => { if (typeof value === 'object' && value !== null && 'type' in value) { - return value.type === 'i64' || value.type === 'u64' + return value.type === JsValueConst.I64 || value.type === JsValueConst.U64 } return false; diff --git a/crates/vertigo/src/driver_module/src_js/jsjson.ts b/crates/vertigo/src/driver_module/src_js/jsjson.ts index 408afbcf..457c1e4a 100644 --- a/crates/vertigo/src/driver_module/src_js/jsjson.ts +++ b/crates/vertigo/src/driver_module/src_js/jsjson.ts @@ -1,5 +1,16 @@ import { BufferCursor, getStringSize } from "./buffer_cursor"; +const JsJsonConst = { + True: 1, + False: 2, + Null: 3, + + String: 4, + Number: 5, + List: 6, + Object: 7, +} as const; + export type JsJsonType = boolean | null @@ -54,27 +65,27 @@ export const jsJsonGetSize = (value: JsJsonType): number => { export const jsJsonDecodeItem = (cursor: BufferCursor): JsJsonType => { const typeParam = cursor.getByte(); - if (typeParam === 1) { + if (typeParam === JsJsonConst.True) { return true; } - if (typeParam === 2) { + if (typeParam === JsJsonConst.False) { return false; } - if (typeParam === 3) { + if (typeParam === JsJsonConst.Null) { return null; } - if (typeParam === 4) { + if (typeParam === JsJsonConst.String) { return cursor.getString(); } - if (typeParam === 5) { + if (typeParam === JsJsonConst.Number) { return cursor.getF64(); } - if (typeParam === 6) { + if (typeParam === JsJsonConst.List) { const out: Array = []; const listSize = cursor.getU32(); @@ -102,34 +113,34 @@ export const jsJsonDecodeItem = (cursor: BufferCursor): JsJsonType => { export const saveJsJsonToBufferItem = (value: JsJsonType, cursor: BufferCursor) => { if (value === true) { - cursor.setByte(1); + cursor.setByte(JsJsonConst.True); return; } if (value === false) { - cursor.setByte(2); + cursor.setByte(JsJsonConst.False); return; } if (value === null) { - cursor.setByte(3); + cursor.setByte(JsJsonConst.Null); return; } if (typeof value === 'string') { - cursor.setByte(4); + cursor.setByte(JsJsonConst.String); cursor.setString(value); return; } if (typeof value === 'number') { - cursor.setByte(5); + cursor.setByte(JsJsonConst.Number); cursor.setF64(value); return; } if (Array.isArray(value)) { - cursor.setByte(6); + cursor.setByte(JsJsonConst.List); cursor.setU32(value.length); for (const item of value) { @@ -146,7 +157,7 @@ export const saveJsJsonToBufferItem = (value: JsJsonType, cursor: BufferCursor) list.push([key, propertyValue]); } - cursor.setByte(7); + cursor.setByte(JsJsonConst.Object); cursor.setU16(list.length); for (const [key, propertyValue] of list) { diff --git a/crates/vertigo/src/driver_module/src_js/jsvalue.ts b/crates/vertigo/src/driver_module/src_js/jsvalue.ts index 829f6a12..552f6409 100644 --- a/crates/vertigo/src/driver_module/src_js/jsvalue.ts +++ b/crates/vertigo/src/driver_module/src_js/jsvalue.ts @@ -3,19 +3,39 @@ import { BufferCursor, getStringSize } from "./buffer_cursor"; import { GuardJsValue } from "./guard"; import { jsJsonDecodeItem, jsJsonGetSize, JsJsonType, saveJsJsonToBufferItem } from "./jsjson"; +export const JsValueConst = { + U32: 1, + I32: 2, + U64: 3, + I64: 4, + F64: 5, + + True: 6, + False: 7, + Null: 8, + Undefined: 9, + + Vec: 10, + String: 11, + List: 12, + Object: 13, + Json: 14, +} as const; + export type JsValueType - = { type: 'u32', value: number, } - | { type: 'i32', value: number, } - | { type: 'u64', value: bigint, } - | { type: 'i64', value: bigint, } + = { type: typeof JsValueConst.U32, value: number, } + | { type: typeof JsValueConst.I32, value: number, } + | { type: typeof JsValueConst.U64, value: bigint, } + | { type: typeof JsValueConst.I64, value: bigint, } + | { type: typeof JsValueConst.F64, value: number, } | boolean | null | undefined | string | Array | Uint8Array - | { type: 'object', value: JsValueMapType } - | { type: 'json', value: JsJsonType }; + | { type: typeof JsValueConst.Object, value: JsValueMapType } + | { type: typeof JsValueConst.Json, value: JsJsonType }; interface JsValueMapType { [key: string]: JsValueType @@ -30,59 +50,66 @@ interface JsValueMapType { const jsValueDecodeItem = (cursor: BufferCursor): JsValueType => { const typeParam = cursor.getByte(); - if (typeParam === 1) { + if (typeParam === JsValueConst.U32) { return { - type: 'u32', + type: JsValueConst.U32, value: cursor.getU32() }; } - if (typeParam === 2) { + if (typeParam === JsValueConst.I32) { return { - type: 'u32', + type: JsValueConst.I32, value: cursor.getI32() }; } - if (typeParam === 3) { + if (typeParam === JsValueConst.U64) { return { - type: 'u64', + type: JsValueConst.U64, value: cursor.getU64() }; } - if (typeParam === 4) { + if (typeParam === JsValueConst.I64) { return { - type: 'i64', + type: JsValueConst.I64, value: cursor.getI64() }; } - if (typeParam === 5) { + if (typeParam === JsValueConst.F64) { + return { + type: JsValueConst.F64, + value: cursor.getF64() + }; + } + + if (typeParam === JsValueConst.True) { return true; } - if (typeParam === 6) { + if (typeParam === JsValueConst.False) { return false; } - if (typeParam === 7) { + if (typeParam === JsValueConst.Null) { return null; } - if (typeParam === 8) { + if (typeParam === JsValueConst.Undefined) { return undefined; } - if (typeParam === 9) { + if (typeParam === JsValueConst.Vec) { return cursor.getBuffer(); } - if (typeParam === 10) { + if (typeParam === JsValueConst.String) { return cursor.getString(); } - if (typeParam === 11) { + if (typeParam === JsValueConst.List) { const out: Array = []; const listSize = cursor.getU32(); @@ -94,7 +121,7 @@ const jsValueDecodeItem = (cursor: BufferCursor): JsValueType => { return out; } - if (typeParam === 12) { + if (typeParam === JsValueConst.Object) { const out: Record = {}; const listSize = cursor.getU16(); @@ -106,16 +133,16 @@ const jsValueDecodeItem = (cursor: BufferCursor): JsValueType => { } return { - type:'object', + type: JsValueConst.Object, value: out }; } - if (typeParam === 13) { + if (typeParam === JsValueConst.Json) { const json = jsJsonDecodeItem(cursor); return { - type: 'json', + type: JsValueConst.Json, value: json }; } @@ -162,15 +189,15 @@ const getSize = (value: JsValueType): number => { return 1 + 4 + value.length; } - if (value.type === 'i32' || value.type === 'u32') { - return 5; //1 + 4 + if (value.type === JsValueConst.I32 || value.type === JsValueConst.U32) { + return 5; // 1 + 4 } - if (value.type === 'i64' || value.type === 'u64') { - return 9; //1 + 8 + if (value.type === JsValueConst.I64 || value.type === JsValueConst.U64 || value.type == JsValueConst.F64) { + return 9; // 1 + 8 } - if (value.type === 'object') { + if (value.type === JsValueConst.Object) { let sum = 1 + 2; for (const [key, propertyValue] of Object.entries(value.value)) { @@ -181,7 +208,7 @@ const getSize = (value: JsValueType): number => { return sum; } - if (value.type === 'json') { + if (value.type === JsValueConst.Json) { return 1 + jsJsonGetSize(value.value); } @@ -190,39 +217,39 @@ const getSize = (value: JsValueType): number => { const saveToBufferItem = (value: JsValueType, cursor: BufferCursor) => { if (value === true) { - cursor.setByte(5); + cursor.setByte(JsValueConst.True); return; } if (value === false) { - cursor.setByte(6); + cursor.setByte(JsValueConst.False); return; } if (value === null) { - cursor.setByte(7); + cursor.setByte(JsValueConst.Null); return; } if (value === undefined) { - cursor.setByte(8); + cursor.setByte(JsValueConst.Undefined); return; } if (value instanceof Uint8Array) { - cursor.setByte(9); + cursor.setByte(JsValueConst.Vec); cursor.setBuffer(value); return; } if (GuardJsValue.isString(value)) { - cursor.setByte(10); + cursor.setByte(JsValueConst.String); cursor.setString(value); return; } if (Array.isArray(value)) { - cursor.setByte(11); + cursor.setByte(JsValueConst.List); cursor.setU32(value.length); for (const item of value) { @@ -232,38 +259,44 @@ const saveToBufferItem = (value: JsValueType, cursor: BufferCursor) => { return; } - if (value.type === 'u32') { - cursor.setByte(1); + if (value.type === JsValueConst.U32) { + cursor.setByte(JsValueConst.U32); cursor.setU32(value.value); return; } - if (value.type === 'i32') { - cursor.setByte(2); + if (value.type === JsValueConst.I32) { + cursor.setByte(JsValueConst.I32); cursor.setI32(value.value); return; } - if (value.type === 'u64') { - cursor.setByte(3); + if (value.type === JsValueConst.U64) { + cursor.setByte(JsValueConst.U64); cursor.setU64(value.value); return; } - if (value.type === 'i64') { - cursor.setByte(4); + if (value.type === JsValueConst.I64) { + cursor.setByte(JsValueConst.I64); cursor.setI64(value.value); return; } - if (value.type === 'object') { + if (value.type === JsValueConst.F64) { + cursor.setByte(JsValueConst.F64); + cursor.setF64(value.value); + return; + } + + if (value.type === JsValueConst.Object) { const list: Array<[string, JsValueType]> = []; for (const [key, propertyValue] of Object.entries(value.value)) { list.push([key, propertyValue]); } - cursor.setByte(12); + cursor.setByte(JsValueConst.Object); cursor.setU16(list.length); for (const [key, propertyValue] of list) { @@ -273,8 +306,8 @@ const saveToBufferItem = (value: JsValueType, cursor: BufferCursor) => { return; } - if (value.type === 'json') { - cursor.setByte(13); + if (value.type === JsValueConst.Json) { + cursor.setByte(JsValueConst.Json); saveJsJsonToBufferItem(value.value, cursor); return; } @@ -344,15 +377,15 @@ export const convertFromJsValue = (value: JsValueType): unknown => { return newList; } - if (value.type === 'u32' || value.type === 'i32') { + if (value.type === JsValueConst.U32 || value.type === JsValueConst.I32) { return value.value; } - if (value.type === 'u64' || value.type === 'i64') { + if (value.type === JsValueConst.U64 || value.type === JsValueConst.I64 || value.type === JsValueConst.F64) { return value.value; } - if (value.type === 'object') { + if (value.type === JsValueConst.Object) { const result: Record = {}; for (const [key, propertyValue] of Object.entries(value.value)) { @@ -362,7 +395,7 @@ export const convertFromJsValue = (value: JsValueType): unknown => { return result; } - if (value.type === 'json') { + if (value.type === JsValueConst.Json) { return value.value; } @@ -380,22 +413,31 @@ export const convertToJsValue = (value: unknown): JsValueType => { } if (typeof value === 'number') { - if (-(2**31) <= value && value < 2**31) { + if (value === (value | 0)) { + // is integer + if (-(2 ** 31) <= value && value < 2 ** 31) { + return { + type: JsValueConst.I32, + value + }; + } + return { - type: 'i32', - value + type: JsValueConst.I64, + value: BigInt(value) }; + } else { + // is float + return { + type: JsValueConst.F64, + value: value, + } } - - return { - type: 'i64', - value: BigInt(value) - }; } if (typeof value === 'bigint') { return { - type: 'i64', + type: JsValueConst.I64, value }; } @@ -408,7 +450,7 @@ export const convertToJsValue = (value: unknown): JsValueType => { try { const json = convertToJsJson(value); return { - type: 'json', + type: JsValueConst.Json, value: json }; } catch (_error) { @@ -421,7 +463,7 @@ export const convertToJsValue = (value: unknown): JsValueType => { } return { - type: 'object', + type: JsValueConst.Object, value: result }; } @@ -430,7 +472,7 @@ export const convertToJsValue = (value: unknown): JsValueType => { try { const list = value.map(convertToJsJson); return { - type: 'json', + type: JsValueConst.Json, value: list }; } catch (_error) { diff --git a/crates/vertigo/src/driver_module/wasm_run.js b/crates/vertigo/src/driver_module/wasm_run.js index 66c668df..9f36ca46 100644 --- a/crates/vertigo/src/driver_module/wasm_run.js +++ b/crates/vertigo/src/driver_module/wasm_run.js @@ -122,18 +122,27 @@ var GuardJsValue; }; GuardJsValue.isNumber = (value) => { if (typeof value === 'object' && value !== null && 'type' in value) { - return value.type === 'i32' || value.type === 'u32'; + return value.type === JsValueConst.I32 || value.type === JsValueConst.U32; } return false; }; GuardJsValue.isBigInt = (value) => { if (typeof value === 'object' && value !== null && 'type' in value) { - return value.type === 'i64' || value.type === 'u64'; + return value.type === JsValueConst.I64 || value.type === JsValueConst.U64; } return false; }; })(GuardJsValue || (GuardJsValue = {})); +const JsJsonConst = { + True: 1, + False: 2, + Null: 3, + String: 4, + Number: 5, + List: 6, + Object: 7, +}; const jsJsonGetSize = (value) => { if (typeof value === 'boolean') { return 1; @@ -164,22 +173,22 @@ const jsJsonGetSize = (value) => { }; const jsJsonDecodeItem = (cursor) => { const typeParam = cursor.getByte(); - if (typeParam === 1) { + if (typeParam === JsJsonConst.True) { return true; } - if (typeParam === 2) { + if (typeParam === JsJsonConst.False) { return false; } - if (typeParam === 3) { + if (typeParam === JsJsonConst.Null) { return null; } - if (typeParam === 4) { + if (typeParam === JsJsonConst.String) { return cursor.getString(); } - if (typeParam === 5) { + if (typeParam === JsJsonConst.Number) { return cursor.getF64(); } - if (typeParam === 6) { + if (typeParam === JsJsonConst.List) { const out = []; const listSize = cursor.getU32(); for (let i = 0; i < listSize; i++) { @@ -199,29 +208,29 @@ const jsJsonDecodeItem = (cursor) => { }; const saveJsJsonToBufferItem = (value, cursor) => { if (value === true) { - cursor.setByte(1); + cursor.setByte(JsJsonConst.True); return; } if (value === false) { - cursor.setByte(2); + cursor.setByte(JsJsonConst.False); return; } if (value === null) { - cursor.setByte(3); + cursor.setByte(JsJsonConst.Null); return; } if (typeof value === 'string') { - cursor.setByte(4); + cursor.setByte(JsJsonConst.String); cursor.setString(value); return; } if (typeof value === 'number') { - cursor.setByte(5); + cursor.setByte(JsJsonConst.Number); cursor.setF64(value); return; } if (Array.isArray(value)) { - cursor.setByte(6); + cursor.setByte(JsJsonConst.List); cursor.setU32(value.length); for (const item of value) { saveJsJsonToBufferItem(item, cursor); @@ -233,7 +242,7 @@ const saveJsJsonToBufferItem = (value, cursor) => { for (const [key, propertyValue] of Object.entries(value)) { list.push([key, propertyValue]); } - cursor.setByte(7); + cursor.setByte(JsJsonConst.Object); cursor.setU16(list.length); for (const [key, propertyValue] of list) { cursor.setString(key); @@ -241,55 +250,77 @@ const saveJsJsonToBufferItem = (value, cursor) => { } }; +const JsValueConst = { + U32: 1, + I32: 2, + U64: 3, + I64: 4, + F64: 5, + True: 6, + False: 7, + Null: 8, + Undefined: 9, + Vec: 10, + String: 11, + List: 12, + Object: 13, + Json: 14, +}; //https://github.com/unsplash/unsplash-js/pull/174 // export type AnyJson = boolean | number | string | null | JsonArray | JsonMap; // export interface JsonMap { [key: string]: AnyJson } // export interface JsonArray extends Array {} const jsValueDecodeItem = (cursor) => { const typeParam = cursor.getByte(); - if (typeParam === 1) { + if (typeParam === JsValueConst.U32) { return { - type: 'u32', + type: JsValueConst.U32, value: cursor.getU32() }; } - if (typeParam === 2) { + if (typeParam === JsValueConst.I32) { return { - type: 'u32', + type: JsValueConst.I32, value: cursor.getI32() }; } - if (typeParam === 3) { + if (typeParam === JsValueConst.U64) { return { - type: 'u64', + type: JsValueConst.U64, value: cursor.getU64() }; } - if (typeParam === 4) { + if (typeParam === JsValueConst.I64) { return { - type: 'i64', + type: JsValueConst.I64, value: cursor.getI64() }; } - if (typeParam === 5) { + if (typeParam === JsValueConst.F64) { + return { + type: JsValueConst.F64, + value: cursor.getF64() + }; + } + if (typeParam === JsValueConst.True) { return true; } - if (typeParam === 6) { + if (typeParam === JsValueConst.False) { return false; } - if (typeParam === 7) { + if (typeParam === JsValueConst.Null) { return null; } - if (typeParam === 8) { + if (typeParam === JsValueConst.Undefined) { return undefined; } - if (typeParam === 9) { + if (typeParam === JsValueConst.Vec) { return cursor.getBuffer(); } - if (typeParam === 10) { + if (typeParam === JsValueConst.String) { return cursor.getString(); } - if (typeParam === 11) { + if (typeParam === JsValueConst.List) { const out = []; const listSize = cursor.getU32(); for (let i = 0; i < listSize; i++) { @@ -297,7 +328,7 @@ const jsValueDecodeItem = (cursor) => { } return out; } - if (typeParam === 12) { + if (typeParam === JsValueConst.Object) { const out = {}; const listSize = cursor.getU16(); for (let i = 0; i < listSize; i++) { @@ -306,14 +337,14 @@ const jsValueDecodeItem = (cursor) => { out[key] = value; } return { - type: 'object', + type: JsValueConst.Object, value: out }; } - if (typeParam === 13) { + if (typeParam === JsValueConst.Json) { const json = jsJsonDecodeItem(cursor); return { - type: 'json', + type: JsValueConst.Json, value: json }; } @@ -350,13 +381,13 @@ const getSize = (value) => { if (value instanceof Uint8Array) { return 1 + 4 + value.length; } - if (value.type === 'i32' || value.type === 'u32') { - return 5; //1 + 4 + if (value.type === JsValueConst.I32 || value.type === JsValueConst.U32) { + return 5; // 1 + 4 } - if (value.type === 'i64' || value.type === 'u64') { - return 9; //1 + 8 + if (value.type === JsValueConst.I64 || value.type === JsValueConst.U64 || value.type == JsValueConst.F64) { + return 9; // 1 + 8 } - if (value.type === 'object') { + if (value.type === JsValueConst.Object) { let sum = 1 + 2; for (const [key, propertyValue] of Object.entries(value.value)) { sum += 4 + getStringSize(key); @@ -364,72 +395,77 @@ const getSize = (value) => { } return sum; } - if (value.type === 'json') { + if (value.type === JsValueConst.Json) { return 1 + jsJsonGetSize(value.value); } return assertNever(); }; const saveToBufferItem = (value, cursor) => { if (value === true) { - cursor.setByte(5); + cursor.setByte(JsValueConst.True); return; } if (value === false) { - cursor.setByte(6); + cursor.setByte(JsValueConst.False); return; } if (value === null) { - cursor.setByte(7); + cursor.setByte(JsValueConst.Null); return; } if (value === undefined) { - cursor.setByte(8); + cursor.setByte(JsValueConst.Undefined); return; } if (value instanceof Uint8Array) { - cursor.setByte(9); + cursor.setByte(JsValueConst.Vec); cursor.setBuffer(value); return; } if (GuardJsValue.isString(value)) { - cursor.setByte(10); + cursor.setByte(JsValueConst.String); cursor.setString(value); return; } if (Array.isArray(value)) { - cursor.setByte(11); + cursor.setByte(JsValueConst.List); cursor.setU32(value.length); for (const item of value) { saveToBufferItem(item, cursor); } return; } - if (value.type === 'u32') { - cursor.setByte(1); + if (value.type === JsValueConst.U32) { + cursor.setByte(JsValueConst.U32); cursor.setU32(value.value); return; } - if (value.type === 'i32') { - cursor.setByte(2); + if (value.type === JsValueConst.I32) { + cursor.setByte(JsValueConst.I32); cursor.setI32(value.value); return; } - if (value.type === 'u64') { - cursor.setByte(3); + if (value.type === JsValueConst.U64) { + cursor.setByte(JsValueConst.U64); cursor.setU64(value.value); return; } - if (value.type === 'i64') { - cursor.setByte(4); + if (value.type === JsValueConst.I64) { + cursor.setByte(JsValueConst.I64); cursor.setI64(value.value); return; } - if (value.type === 'object') { + if (value.type === JsValueConst.F64) { + cursor.setByte(JsValueConst.F64); + cursor.setF64(value.value); + return; + } + if (value.type === JsValueConst.Object) { const list = []; for (const [key, propertyValue] of Object.entries(value.value)) { list.push([key, propertyValue]); } - cursor.setByte(12); + cursor.setByte(JsValueConst.Object); cursor.setU16(list.length); for (const [key, propertyValue] of list) { cursor.setString(key); @@ -437,8 +473,8 @@ const saveToBufferItem = (value, cursor) => { } return; } - if (value.type === 'json') { - cursor.setByte(13); + if (value.type === JsValueConst.Json) { + cursor.setByte(JsValueConst.Json); saveJsJsonToBufferItem(value.value, cursor); return; } @@ -487,20 +523,20 @@ const convertFromJsValue = (value) => { } return newList; } - if (value.type === 'u32' || value.type === 'i32') { + if (value.type === JsValueConst.U32 || value.type === JsValueConst.I32) { return value.value; } - if (value.type === 'u64' || value.type === 'i64') { + if (value.type === JsValueConst.U64 || value.type === JsValueConst.I64 || value.type === JsValueConst.F64) { return value.value; } - if (value.type === 'object') { + if (value.type === JsValueConst.Object) { const result = {}; for (const [key, propertyValue] of Object.entries(value.value)) { result[key] = convertFromJsValue(propertyValue); } return result; } - if (value.type === 'json') { + if (value.type === JsValueConst.Json) { return value.value; } return assertNever(); @@ -514,20 +550,30 @@ const convertToJsValue = (value) => { return value; } if (typeof value === 'number') { - if (-(2 ** 31) <= value && value < 2 ** 31) { + if (value === (value | 0)) { + // is integer + if (-(2 ** 31) <= value && value < 2 ** 31) { + return { + type: JsValueConst.I32, + value + }; + } return { - type: 'i32', - value + type: JsValueConst.I64, + value: BigInt(value) + }; + } + else { + // is float + return { + type: JsValueConst.F64, + value: value, }; } - return { - type: 'i64', - value: BigInt(value) - }; } if (typeof value === 'bigint') { return { - type: 'i64', + type: JsValueConst.I64, value }; } @@ -538,7 +584,7 @@ const convertToJsValue = (value) => { try { const json = convertToJsJson(value); return { - type: 'json', + type: JsValueConst.Json, value: json }; } @@ -549,7 +595,7 @@ const convertToJsValue = (value) => { result[propName] = convertToJsValue(propValue); } return { - type: 'object', + type: JsValueConst.Object, value: result }; } @@ -557,7 +603,7 @@ const convertToJsValue = (value) => { try { const list = value.map(convertToJsJson); return { - type: 'json', + type: JsValueConst.Json, value: list }; } @@ -779,7 +825,7 @@ const catchError = async (wasm, callback_id, response, callbackSuccess) => { const responseMessage = new String(error).toString(); wasm.wasm_callback(callback_id, [ false, - { type: 'u32', value: response.status }, + { type: JsValueConst.U32, value: response.status }, responseMessage //body (string) ]); } @@ -843,9 +889,9 @@ class Fetch { const json = await response.json(); wasm.wasm_callback(callback_id, [ true, - { type: 'u32', value: response.status }, + { type: JsValueConst.U32, value: response.status }, { - type: 'json', + type: JsValueConst.Json, value: json } ]); @@ -857,7 +903,7 @@ class Fetch { const text = await response.text(); wasm.wasm_callback(callback_id, [ true, - { type: 'u32', value: response.status }, + { type: JsValueConst.U32, value: response.status }, text //body (text) ]); }); @@ -868,7 +914,7 @@ class Fetch { const textUint8Array = new Uint8Array(text); wasm.wasm_callback(callback_id, [ true, - { type: 'u32', value: response.status }, + { type: JsValueConst.U32, value: response.status }, textUint8Array //body (text) ]); }); @@ -878,7 +924,7 @@ class Fetch { const responseMessage = new String(err).toString(); wasm.wasm_callback(callback_id, [ false, - { type: 'u32', value: 0 }, + { type: JsValueConst.U32, value: 0 }, responseMessage //body (string) ]); } diff --git a/crates/vertigo/src/lib.rs b/crates/vertigo/src/lib.rs index 1b397bba..8de4e340 100644 --- a/crates/vertigo/src/lib.rs +++ b/crates/vertigo/src/lib.rs @@ -232,6 +232,42 @@ pub use vertigo_macro::AutoJsJson; /// ``` pub use vertigo_macro::component; +/// Macro that allows to call methods on JavaScript `window` object +/// +/// Example 1: +/// +/// ```rust +/// use vertigo::window; +/// +/// let max_y = window!("scrollMaxY"); +/// window!("scrollTo()", 0, max_y); +/// ``` +/// +/// Example 2: +/// +/// ```rust +/// use vertigo::window; +/// +/// window!("scrollTo()", +/// vec![ +/// ("top", 100000.into()), +/// ("behavior", "smooth".into()), +/// ] +/// ); +/// ``` +pub use vertigo_macro::window; + +/// Macro that allows to call methods on JavaScript `document` object +/// +/// Example: +/// +/// ```rust +/// use vertigo::document; +/// +/// let referrer = document!("referrer"); +/// ``` +pub use vertigo_macro::document; + /// Marco that marks an entry point of the app /// /// Note: Html, head and body tags are required by vertigo to properly take over the DOM diff --git a/crates/vertigo/src/tests/api_access/mod.rs b/crates/vertigo/src/tests/api_access/mod.rs new file mode 100644 index 00000000..1d4f8527 --- /dev/null +++ b/crates/vertigo/src/tests/api_access/mod.rs @@ -0,0 +1,23 @@ +#[test] +fn test_window() { + use crate as vertigo; + use crate::window; + + let x = 5; + let foo: &str = "foo"; + let bar = "bar".to_string(); + + window!( + "aFunctionRichInArguments()", + 3, + -34, + "blablabla", + true, + false, + 34.56, + x, + -x, + foo, + bar, + ); +} diff --git a/crates/vertigo/src/tests/mod.rs b/crates/vertigo/src/tests/mod.rs index c1c38b89..7589ec78 100644 --- a/crates/vertigo/src/tests/mod.rs +++ b/crates/vertigo/src/tests/mod.rs @@ -1,6 +1,7 @@ // Tests that can't be placed directly alongside the code, // for example tests of macros. +mod api_access; mod autojsjson; mod bind; mod css; diff --git a/demo/app/src/app/js_api_access/mod.rs b/demo/app/src/app/js_api_access/mod.rs new file mode 100644 index 00000000..c930f4d1 --- /dev/null +++ b/demo/app/src/app/js_api_access/mod.rs @@ -0,0 +1,60 @@ +use vertigo::{bind, component, css, document, dom, dom_element, window, JsValue, Value}; + +#[derive(Default, PartialEq)] +pub struct State { + answer: Value, +} + +#[component] +pub fn JsApiAccess() { + let state = State::default(); + + let container_css = css!{" + "}; + + let items = dom_element!{
    } + .children( + (1..201).map(|i| dom! {
  1. "List item" {i}
  2. }).collect() + ); + + let to_bottom = || { + let max_y = window!("scrollMaxY"); + vertigo::log::info!("max_y = {:?}", max_y); + window!("scrollTo()", 0, max_y); + }; + + let down_smooth = || { + let max_y = window!("scrollMaxY"); + vertigo::log::info!("max_y = {:?}", max_y); + window!("scrollTo()", + vec![ + ("top", 100000.into()), + ("behavior", "smooth".into()), + ] + ); + }; + + let ask = bind!(state.answer, || { + let js_answer = window!("prompt()", "How are you?"); + if let JsValue::String(js_answer) = js_answer { + answer.set(js_answer) + } + }); + + dom! { +
    +

    + + + + +

    +

    + + " Answer: " {state.answer} +

    + {items} + +
    + } +} diff --git a/demo/app/src/app/mod.rs b/demo/app/src/app/mod.rs index 15563201..cd404e74 100644 --- a/demo/app/src/app/mod.rs +++ b/demo/app/src/app/mod.rs @@ -4,6 +4,7 @@ mod dropfiles; mod game_of_life; mod github_explorer; mod input; +mod js_api_access; mod render; mod route; mod state; diff --git a/demo/app/src/app/render.rs b/demo/app/src/app/render.rs index 948d2ec7..8e2b040f 100644 --- a/demo/app/src/app/render.rs +++ b/demo/app/src/app/render.rs @@ -9,6 +9,7 @@ use super::{ game_of_life::GameOfLife, github_explorer::GitHubExplorer, input::MyInput, + js_api_access::JsApiAccess, route::Route, styling::Styling, sudoku::Sudoku, @@ -81,6 +82,7 @@ fn render_header(state: &app::State) -> DomNode { { render_menu_item(state.route.route.clone(), Route::Chat) } { render_menu_item(state.route.route.clone(), Route::Todo) } { render_menu_item(state.route.route.clone(), Route::DropFile) } + { render_menu_item(state.route.route.clone(), Route::JsApiAccess) } } @@ -94,7 +96,6 @@ fn title_value(state: app::State) -> Computed { let route = state.route.route.get(context); match route { - Route::Styling => "Styling".into(), Route::Counters => { let sum = sum.get(context); format!("Counter = {sum}") @@ -104,12 +105,7 @@ fn title_value(state: app::State) -> Computed { let input_value = input_value.get(context); format!("Input => {input_value}") } - Route::GithubExplorer => "GithubExplorer".into(), - Route::GameOfLife => "GameOfLife".into(), - Route::Chat => "Chat".into(), - Route::Todo => "Todo".into(), - Route::DropFile => "DropFile".into(), - Route::NotFound => "NotFound".into(), + _ => route.label().to_string(), } }) } @@ -132,6 +128,7 @@ pub fn render(state: &app::State) -> DomNode { Route::Chat => dom! { }, Route::Todo => dom! { }, Route::DropFile => dom! { }, + Route::JsApiAccess => dom! { }, Route::NotFound => dom! {
    "Page Not Found"
    }, } }); diff --git a/demo/app/src/app/route.rs b/demo/app/src/app/route.rs index 54e5d0ad..726aabb1 100644 --- a/demo/app/src/app/route.rs +++ b/demo/app/src/app/route.rs @@ -11,6 +11,7 @@ pub enum Route { Chat, Todo, DropFile, + JsApiAccess, NotFound, } @@ -32,6 +33,7 @@ impl Route { "/chat" => Self::Chat, "/todo" => Self::Todo, "/drop-file" => Self::DropFile, + "/js-api-access" => Self::JsApiAccess, _ => Self::NotFound, } } @@ -47,6 +49,7 @@ impl Route { Self::Chat => "Chat", Self::Todo => "Todo", Self::DropFile => "Drop File", + Self::JsApiAccess => "JS Api Access", Self::NotFound => "Not Found", } } @@ -70,6 +73,7 @@ impl Display for Route { Self::Chat => "/chat", Self::Todo => "/todo", Self::DropFile => "/drop-file", + Self::JsApiAccess => "/js-api-access", Self::NotFound => "/not-found", }; f.write_str(str) diff --git a/demo/app/src/app/sudoku/component/mod.rs b/demo/app/src/app/sudoku/component/mod.rs index 57291d85..2839f7da 100644 --- a/demo/app/src/app/sudoku/component/mod.rs +++ b/demo/app/src/app/sudoku/component/mod.rs @@ -127,7 +127,7 @@ fn render_cell(item: &Cell) -> (u32, u32, DomNode) { let cell_width = 2 * border + 3 * small_item_width; let cell_height = 2 * border + 3 * small_item_height; - let value_view = item.number.value.render_value({ + let value_view = item.number.render_value({ let item = item.clone(); move |value| { if let Some(value) = value { diff --git a/demo/app/src/app/sudoku/component/render_cell_possible.rs b/demo/app/src/app/sudoku/component/render_cell_possible.rs index 901e691e..b0ef2cd8 100644 --- a/demo/app/src/app/sudoku/component/render_cell_possible.rs +++ b/demo/app/src/app/sudoku/component/render_cell_possible.rs @@ -55,7 +55,7 @@ fn view_one_possible(cell_width: u32, cell: &Cell) -> DomNode { for number in possible.iter() { let on_set = bind!(cell, number, || { - cell.number.value.set(Some(number)); + cell.number.set(Some(number)); }); wrapper.add_child(dom! { @@ -83,7 +83,7 @@ fn view_last_value(cell_width: u32, cell: &Cell, possible_last_value: SudokuValu let on_set = Computed::from(bind!(cell, possible_last_value, |_context| -> Rc { Rc::new(bind!(cell, possible_last_value, || { - cell.number.value.set(Some(possible_last_value)); + cell.number.set(Some(possible_last_value)); })) })); @@ -122,7 +122,7 @@ fn view_default(cell_width: u32, cell: &Cell, possible: HashSet) -> let on_click = bind_rc!(cell, should_show, number, || { if should_show { - cell.number.value.set(Some(number)); + cell.number.set(Some(number)); } }); diff --git a/demo/app/src/app/sudoku/component/render_cell_value.rs b/demo/app/src/app/sudoku/component/render_cell_value.rs index 4c1b13af..f80af8a9 100644 --- a/demo/app/src/app/sudoku/component/render_cell_value.rs +++ b/demo/app/src/app/sudoku/component/render_cell_value.rs @@ -11,7 +11,7 @@ pub fn render_cell_value(item_height_size: u32, value: SudokuValue, cell: &Cell) match show_delete { true => { let on_click = bind!(cell, || { - cell.number.value.set(None); + cell.number.set(None); }); let css_delete = css!(" diff --git a/demo/app/src/app/sudoku/state/mod.rs b/demo/app/src/app/sudoku/state/mod.rs index 944334ab..0564f0ad 100644 --- a/demo/app/src/app/sudoku/state/mod.rs +++ b/demo/app/src/app/sudoku/state/mod.rs @@ -15,9 +15,9 @@ pub mod sudoku_square; pub mod tree_box; fn create_grid() -> SudokuSquare> { - SudokuSquare::create_with_iterator(move |level0x, level0y| { - SudokuSquare::create_with_iterator(move |level1x, level1y| { - NumberItem::new(level0x, level0y, level1x, level1y, None) + SudokuSquare::create_with_iterator(move |_level0x, _level0y| { + SudokuSquare::create_with_iterator(move |_level1x, _level1y| { + NumberItem::new(None) }) }) } @@ -96,7 +96,7 @@ impl SudokuState { for y0 in TreeBoxIndex::variants() { for x1 in TreeBoxIndex::variants() { for y1 in TreeBoxIndex::variants() { - self.grid.get_from(x0, y0).get_from(x1, y1).number.value.set(None); + self.grid.get_from(x0, y0).get_from(x1, y1).number.set(None); } } } diff --git a/demo/app/src/app/sudoku/state/number_item.rs b/demo/app/src/app/sudoku/state/number_item.rs index e47fd08c..dd66431e 100644 --- a/demo/app/src/app/sudoku/state/number_item.rs +++ b/demo/app/src/app/sudoku/state/number_item.rs @@ -1,7 +1,5 @@ use vertigo::Value; -use super::tree_box::TreeBoxIndex; - #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub enum SudokuValue { Value1, @@ -45,31 +43,31 @@ impl SudokuValue { } } -// pub type NumberItem = Value>; +pub type NumberItem = Value>; -#[derive(Clone)] -pub struct NumberItem { - pub x0: TreeBoxIndex, - pub y0: TreeBoxIndex, - pub x1: TreeBoxIndex, - pub y1: TreeBoxIndex, - pub value: Value>, -} +// #[derive(Clone)] +// pub struct NumberItem { +// pub x0: TreeBoxIndex, +// pub y0: TreeBoxIndex, +// pub x1: TreeBoxIndex, +// pub y1: TreeBoxIndex, +// pub value: Value>, +// } -impl NumberItem { - pub fn new( - x0: TreeBoxIndex, - y0: TreeBoxIndex, - x1: TreeBoxIndex, - y1: TreeBoxIndex, - value: Option, - ) -> NumberItem { - NumberItem { - x0, - y0, - x1, - y1, - value: Value::new(value), - } - } -} +// impl NumberItem { +// pub fn new( +// x0: TreeBoxIndex, +// y0: TreeBoxIndex, +// x1: TreeBoxIndex, +// y1: TreeBoxIndex, +// value: Option, +// ) -> NumberItem { +// NumberItem { +// x0, +// y0, +// x1, +// y1, +// value: Value::new(value), +// } +// } +// } diff --git a/demo/app/src/app/sudoku/state/possible_values.rs b/demo/app/src/app/sudoku/state/possible_values.rs index 323214c4..bcf0937a 100644 --- a/demo/app/src/app/sudoku/state/possible_values.rs +++ b/demo/app/src/app/sudoku/state/possible_values.rs @@ -33,7 +33,7 @@ pub fn possible_values( for x0 in TreeBoxIndex::variants() { for x1 in TreeBoxIndex::variants() { let value = grid.get_from(x0, level0y).get_from(x1, level1y); - let value = value.value.get(context); + let value = value.get(context); if let Some(value) = value { current_numbers_in_ceis.remove(&value); } @@ -44,7 +44,7 @@ pub fn possible_values( for y0 in TreeBoxIndex::variants() { for y1 in TreeBoxIndex::variants() { let value = grid.get_from(level0x, y0).get_from(level1x, y1); - let value = value.value.get(context); + let value = value.get(context); if let Some(value) = value { current_numbers_in_ceis.remove(&value); } @@ -55,7 +55,7 @@ pub fn possible_values( for x1 in TreeBoxIndex::variants() { for y1 in TreeBoxIndex::variants() { let value = grid.get_from(level0x, level0y).get_from(x1, y1); - let value = value.value.get(context); + let value = value.get(context); if let Some(value) = value { current_numbers_in_ceis.remove(&value); } diff --git a/demo/app/src/app/sudoku/state/possible_values_last.rs b/demo/app/src/app/sudoku/state/possible_values_last.rs index c6698d4e..05c33463 100644 --- a/demo/app/src/app/sudoku/state/possible_values_last.rs +++ b/demo/app/src/app/sudoku/state/possible_values_last.rs @@ -35,7 +35,7 @@ where for (check_x0, check_x1) in iterate_by() { let cell = select_from_grid(check_x0, check_x1); - let input_value = cell.input.value.get(context); + let input_value = cell.input.get(context); if input_value.is_none() && cell.possible.get(context).contains(possible_value) { count += 1; diff --git a/tests/basic/tests.rs b/tests/basic/tests.rs index 44888d22..220d290d 100644 --- a/tests/basic/tests.rs +++ b/tests/basic/tests.rs @@ -104,19 +104,36 @@ async fn basic() { let start = std::time::Instant::now(); // click "Generate" - c.find(Locator::Id("generate")).await.expect("div4: find generate button failed") - .click().await.expect("div4: click generate failed"); + { + c.find(Locator::Id("generate")).await.expect("div4: find generate button failed") + .click().await.expect("div4: click generate failed"); - let click_time = start.elapsed(); + let click_time = start.elapsed(); - println!("div4: Generate took {} ms", click_time.as_millis()); + println!("div4: Generate took {} ms", click_time.as_millis()); - c.find(Locator::Id("row-9999")).await.expect("div4: find row-9999 failed"); + c.find(Locator::Id("row-9999")).await.expect("div4: find row-9999 failed"); - let row999_time = start.elapsed(); + let row999_time = start.elapsed(); + + println!("div4: Row 9999 found {} ms after click", row999_time.as_millis()); + } + + // click "Generate" again + { + c.find(Locator::Id("generate")).await.expect("div4: find generate button failed") + .click().await.expect("div4-2: click generate failed"); + + let click_time = start.elapsed(); + + println!("div4-2: Generate took {} ms", click_time.as_millis()); + + c.find(Locator::Id("row-9999")).await.expect("div4-2: find row-9999 failed"); - println!("div4: Row 9999 found {} ms after click", row999_time.as_millis()); + let row999_time = start.elapsed(); + println!("div4-2: Row 9999 found {} ms after click", row999_time.as_millis()); + } println!("Closing browser");