diff --git a/fastn-core/src/package/package_doc.rs b/fastn-core/src/package/package_doc.rs index 3b9c17bce3..f7786720db 100644 --- a/fastn-core/src/package/package_doc.rs +++ b/fastn-core/src/package/package_doc.rs @@ -514,14 +514,10 @@ pub(crate) async fn read_ftd_2023( } let js_ast_data = ftd::js::document_into_js_ast(main_ftd_doc); - let js_document_script = fastn_js::to_js( - js_ast_data.asts.as_slice(), - true, - config.package.name.as_str(), - ); + let js_document_script = + fastn_js::to_js(js_ast_data.asts.as_slice(), config.package.name.as_str()); let js_ftd_script = fastn_js::to_js( ftd::js::default_bag_into_js_ast().as_slice(), - false, config.package.name.as_str(), ); let ssr_body = fastn_js::ssr_with_js_string( diff --git a/fastn-js/js/ftd.js b/fastn-js/js/ftd.js index e8d325b06b..2fefbab34f 100644 --- a/fastn-js/js/ftd.js +++ b/fastn-js/js/ftd.js @@ -1,12 +1,20 @@ -let ftd = { - // source: https://stackoverflow.com/questions/400212/ (cc-by-sa) - riveNodes: {}, - is_empty(value) { +const ftd = (function() { + const exports = {}; + + const riveNodes = {}; + + const global = {}; + + exports.global = global; + + exports.riveNodes = riveNodes; + + exports.is_empty = value => { value = fastn_utils.getFlattenStaticValue(value); return fastn_utils.isNull(value) || value.length === 0; - }, + }; - len(data) { + exports.len = data => { if (!!data && data instanceof fastn.mutableListClass) { if (data.getLength) return data.getLength(); @@ -14,15 +22,15 @@ let ftd = { } if (!!data && data instanceof fastn.mutableClass) { let inner_data = data.get(); - return ftd.len(inner_data); + return exports.len(inner_data); } if (!!data && data.length) { return data.length; } return -2; - }, + }; - copy_to_clipboard(args) { + exports.copy_to_clipboard = args => { let text = args.a; if (text instanceof fastn.mutableClass) text = fastn_utils.getStaticValue(text); if (text.startsWith("\\", 0)) { @@ -37,16 +45,14 @@ let ftd = { }, function(err) { console.error('Async: Could not copy text: ', err); }); - }, + }; // Todo: Implement this (Remove highlighter) - clean_code(args) { - return args.a; - }, + exports.clean_code = args => args.a; - set_rive_boolean(args, node) { + exports.set_rive_boolean = (args, node) => { if (!!args.rive) { - let riveNode = ftd.riveNodes[`${args.rive}__${ftd.device.get()}`]; + let riveNode = riveNodes[`${args.rive}__${exports.device.get()}`]; node = riveNode ? riveNode: node; } let riveConst = node.getExtraData().rive; @@ -54,11 +60,11 @@ let ftd = { const inputs = riveConst.stateMachineInputs(stateMachineName); const bumpTrigger = inputs.find(i => i.name === args.input); bumpTrigger.value = args.value; - }, + }; - toggle_rive_boolean(args, node) { + exports.toggle_rive_boolean = (args, node) => { if (!!args.rive) { - let riveNode = ftd.riveNodes[`${args.rive}__${ftd.device.get()}`]; + let riveNode = riveNodes[`${args.rive}__${exports.device.get()}`]; node = riveNode ? riveNode: node; } let riveConst = node.getExtraData().rive; @@ -66,11 +72,11 @@ let ftd = { const inputs = riveConst.stateMachineInputs(stateMachineName); const trigger = inputs.find(i => i.name === args.input); trigger.value = !trigger.value; - }, + }; - set_rive_integer(args, node) { + exports.set_rive_integer = (args, node) => { if (!!args.rive) { - let riveNode = ftd.riveNodes[`${args.rive}__${ftd.device.get()}`]; + let riveNode = riveNodes[`${args.rive}__${exports.device.get()}`]; node = riveNode ? riveNode: node; } let riveConst = node.getExtraData().rive; @@ -78,11 +84,11 @@ let ftd = { const inputs = riveConst.stateMachineInputs(stateMachineName); const trigger = inputs.find(i => i.name === args.input); trigger.value = args.value; - }, + }; - fire_rive(args, node) { + exports.fire_rive = (args, node) => { if (!!args.rive) { - let riveNode = ftd.riveNodes[`${args.rive}__${ftd.device.get()}`]; + let riveNode = riveNodes[`${args.rive}__${exports.device.get()}`]; node = riveNode ? riveNode: node; } let riveConst = node.getExtraData().rive; @@ -90,40 +96,40 @@ let ftd = { const inputs = riveConst.stateMachineInputs(stateMachineName); const trigger = inputs.find(i => i.name === args.input); trigger.fire(); - }, + }; - play_rive(args, node) { + exports.play_rive = (args, node) => { if (!!args.rive) { - let riveNode = ftd.riveNodes[`${args.rive}__${ftd.device.get()}`]; + let riveNode = riveNodes[`${args.rive}__${exports.device.get()}`]; node = riveNode ? riveNode: node; } node.getExtraData().rive.play(args.input); - }, + }; - pause_rive(args, node) { + exports.pause_rive = (args, node) => { if (!!args.rive) { - let riveNode = ftd.riveNodes[`${args.rive}__${ftd.device.get()}`]; + let riveNode = riveNodes[`${args.rive}__${exports.device.get()}`]; node = riveNode ? riveNode: node; } node.getExtraData().rive.pause(args.input); - }, + }; - toggle_play_rive(args, node) { + exports.toggle_play_rive = (args, node) => { if (!!args.rive) { - let riveNode = ftd.riveNodes[`${args.rive}__${ftd.device.get()}`]; + let riveNode = riveNodes[`${args.rive}__${exports.device.get()}`]; node = riveNode ? riveNode: node; } let riveConst = node.getExtraData().rive riveConst.playingAnimationNames.includes(args.input) ? riveConst.pause(args.input) : riveConst.play(args.input); - }, + }; - get(value, index) { + exports.get = (value, index) => { return fastn_utils.getStaticValue(fastn_utils.getterByKey(value, index)); - }, + }; - component_data(component) { + exports.component_data = component => { let attributesIndex = component.getAttribute(fastn_dom.webComponentArgument); let attributes = fastn_dom.webComponent[attributesIndex]; return Object.fromEntries( @@ -142,115 +148,199 @@ let ftd = { } ) ); - } -}; - -// ftd.append($a = $people, v = Tom) -ftd.append = function (list, item) { list.push(item) } -ftd.pop = function (list) { list.pop() } -ftd.insert_at = function (list, index, item) { list.insertAt(index, item) } -ftd.delete_at = function (list, index) { list.deleteAt(index) } -ftd.clear_all = function (list) { list.clearAll() } -ftd.clear = ftd.clear_all; -ftd.set_list = function (list, value) { list.set(value) } - -ftd.http = function (url, method, body, headers) { - if (url instanceof fastn.mutableClass) url = url.get(); - if (method instanceof fastn.mutableClass) method = method.get(); - method = method.trim().toUpperCase(); - const init = { - method, - headers: {} }; - if(headers && headers instanceof fastn.recordInstanceClass) { - Object.assign(init.headers, headers.toObject()); - } - if(method !== 'GET') { - init.headers['Content-Type'] = 'application/json'; + + exports.append = function (list, item) { list.push(item) }; + exports.pop = function (list) { list.pop() }; + exports.insert_at = function (list, index, item) { list.insertAt(index, item) }; + exports.delete_at = function (list, index) { list.deleteAt(index) } + exports.clear_all = function (list) { list.clearAll() } + exports.clear = exports.clear_all; + exports.set_list = function (list, value) { list.set(value) } + + exports.http = function (url, method, body, headers) { + if (url instanceof fastn.mutableClass) url = url.get(); + if (method instanceof fastn.mutableClass) method = method.get(); + method = method.trim().toUpperCase(); + const init = { + method, + headers: {} + }; + if(headers && headers instanceof fastn.recordInstanceClass) { + Object.assign(init.headers, headers.toObject()); + } + if(method !== 'GET') { + init.headers['Content-Type'] = 'application/json'; + } + if(body && body instanceof fastn.recordInstanceClass && method !== 'GET') { + init.body = JSON.stringify(body.toObject()); + } + fetch(url, init) + .then(res => { + if(!res.ok) { + return new Error("[http]: Request failed", res) + } + + return res.json(); + }) + .then(json => { + console.log("[http]: Response OK", json); + }) + .catch(console.error); } - if(body && body instanceof fastn.recordInstanceClass && method !== 'GET') { - init.body = JSON.stringify(body.toObject()); + + exports.navigate = function(url, request_data) { + let query_parameters = new URLSearchParams(); + if(request_data instanceof RecordInstance) { + // @ts-ignore + for (let [header, value] of Object.entries(request_data.toObject())) { + if (header != "url" && header != "function" && header != "method") { + let [key, val] = value.length == 2 ? value : [header, value]; + query_parameters.set(key, val); + } + } + } + let query_string = query_parameters.toString(); + if (query_string) { + let get_url = url + "?" + query_parameters.toString(); + window.location.href = get_url; + } + else { + window.location.href = url; + } } - fetch(url, init) - .then(res => { - if(!res.ok) { - return new Error("[http]: Request failed", res) + + exports.toggle_dark_mode = function () { + const is_dark_mode = exports.get(exports.dark_mode); + if(is_dark_mode) { + enable_light_mode(); + } else { + enable_dark_mode(); } + }; + + exports.local_storage = { + _get_key(key) { + if (key instanceof fastn.mutableClass) { + key = key.get(); + } + const packageNamePrefix = __fastn_package_name__ ? `${__fastn_package_name__}_` : ""; + const snakeCaseKey = fastn_utils.toSnakeCase(key); + + return `${packageNamePrefix}${snakeCaseKey}`; + }, + set(key, value) { + key = this._get_key(key); + value = fastn_utils.getFlattenStaticValue(value); + localStorage.setItem(key, value && typeof value === 'object' ? JSON.stringify(value) : value); + }, + get(key) { + key = this._get_key(key); + if(ssr && !hydrating) { + return; + } + const item = localStorage.getItem(key); + if(!item) { + return; + } + try { + const obj = JSON.parse(item); - return res.json(); - }) - .then(json => { - console.log("[http]: Response OK", json); - }) - .catch(console.error); -} - -ftd.navigate = function(url, request_data) { - let query_parameters = new URLSearchParams(); - if(request_data instanceof RecordInstance) { - // @ts-ignore - for (let [header, value] of Object.entries(request_data.toObject())) { - if (header != "url" && header != "function" && header != "method") { - let [key, val] = value.length == 2 ? value : [header, value]; - query_parameters.set(key, val); + return fastn_utils.staticToMutables(obj); + } catch { + return item; } + }, + delete(key) { + key = this._get_key(key); + localStorage.removeItem(key); } } - let query_string = query_parameters.toString(); - if (query_string) { - let get_url = url + "?" + query_parameters.toString(); - window.location.href = get_url; - } - else { - window.location.href = url; - } -} - -ftd.toggle_dark_mode = function () { - const is_dark_mode = ftd.get(ftd.dark_mode); - if(is_dark_mode) { - enable_light_mode(); - } else { - enable_dark_mode(); - } -}; -const len = ftd.len; + // LEGACY -ftd.local_storage = { - _get_key(key) { - if (key instanceof fastn.mutableClass) { - key = key.get(); + function legacyNameToJS(s) { + let name = s.toString(); + + if (name[0].charCodeAt(0) >= 48 && name[0].charCodeAt(0) <= 57) { + name = '_' + name; } - const packageNamePrefix = __fastn_package_name__ ? `${__fastn_package_name__}_` : ""; - const snakeCaseKey = fastn_utils.toSnakeCase(key); - return `${packageNamePrefix}${snakeCaseKey}`; - }, - set(key, value) { - key = this._get_key(key); - value = fastn_utils.getFlattenStaticValue(value); - localStorage.setItem(key, value && typeof value === 'object' ? JSON.stringify(value) : value); - }, - get(key) { - key = this._get_key(key); - if(ssr && !hydrating) { + return name + .replaceAll('#', "__") + .replaceAll('-', "_") + .replaceAll(':', "___") + .replaceAll(',', "$") + .replaceAll('\\', "/") + .replaceAll('/', '_') + .replaceAll('.', "_"); + } + + function getDocNameAndRemaining(s) { + let part1 = ""; + let patternToSplitAt = s; + + const split1 = s.split('#'); + if (split1.length === 2) { + part1 = split1[0] + '#'; + patternToSplitAt = split1[1]; + } + + const split2 = patternToSplitAt.split('.'); + if (split2.length === 2) { + return [part1 + split2[0], split2[1]]; + } else { + return [s, null]; + } + } + + function isMutable(obj) { + return obj instanceof fastn.mutableClass || + obj instanceof fastn.mutableListClass || + obj instanceof fastn.recordInstanceClass; + } + + exports.set_value = function(variable, value) { + const [var_name, remaining] = getDocNameAndRemaining(variable); + let name = legacyNameToJS(var_name); + if(global[name] === undefined) { + console.log(`[ftd-legacy]: ${variable} is not in global map, ignoring`); return; } - const item = localStorage.getItem(key); - if(!item) { + const mutable = global[name]; + if(!isMutable(mutable)) { + console.log(`[ftd-legacy]: ${variable} is not a mutable, ignoring`); return; } - try { - const obj = JSON.parse(item); + if(remaining) { + mutable.get(remaining).set(value); + } else { + mutable.set(value); + } + } - return fastn_utils.staticToMutables(obj); - } catch { - return item; + exports.get_value = function(variable) { + const [var_name, remaining] = getDocNameAndRemaining(variable); + let name = legacyNameToJS(var_name); + if(global[name] === undefined) { + console.log(`[ftd-legacy]: ${variable} is not in global map, ignoring`); + return; + } + const value = global[name]; + if(isMutable(value)) { + if(remaining) { + return value.get(remaining); + } else { + return value.get(); + } + } else { + return value; } - }, - delete(key) { - key = this._get_key(key); - localStorage.removeItem(key); } -} + + return exports; +})(); + +const len = ftd.len; + +const global = ftd.global; diff --git a/fastn-js/src/constants.rs b/fastn-js/src/constants.rs index 364657973f..d32bc6e9a7 100644 --- a/fastn-js/src/constants.rs +++ b/fastn-js/src/constants.rs @@ -1,4 +1,5 @@ pub const GLOBAL_VARIABLE_MAP: &str = "global"; +pub const LEGACY_GLOBAL_MAP_REF_VARIABLE: &str = "__fastn_legacy_global_ref__"; pub const LOCAL_VARIABLE_MAP: &str = "__args__"; pub const FUNCTION_ARGS: &str = "args"; pub const INHERITED_PREFIX: &str = "__$$inherited$$__"; diff --git a/fastn-js/src/ssr.rs b/fastn-js/src/ssr.rs index 78b3900189..f4d6cba9e4 100644 --- a/fastn-js/src/ssr.rs +++ b/fastn-js/src/ssr.rs @@ -30,7 +30,7 @@ pub fn ssr(ast: &[fastn_js::Ast]) -> String { parenti0.setProperty(fastn_dom.PropertyKind.Height, fastn_dom.Resizing.FillContainer, inherited); main(parenti0); }}; - fastn_virtual.ssr(main_wrapper);", fastn_js::to_js(ast, false, + fastn_virtual.ssr(main_wrapper);", fastn_js::to_js(ast, "foo")); ssr_str(&js) } diff --git a/fastn-js/src/to_js.rs b/fastn-js/src/to_js.rs index c30a10cd2b..806e641511 100644 --- a/fastn-js/src/to_js.rs +++ b/fastn-js/src/to_js.rs @@ -10,14 +10,9 @@ fn comma() -> pretty::RcDoc<'static> { pretty::RcDoc::text(",".to_string()) } -pub fn to_js(ast: &[fastn_js::Ast], is_global_need: bool, package_name: &str) -> String { +pub fn to_js(ast: &[fastn_js::Ast], package_name: &str) -> String { let mut w = Vec::new(); - let o = if is_global_need { - get_variable_declaration("global") - } else { - pretty::RcDoc::nil() - } - .append(pretty::RcDoc::intersperse( + let o = pretty::RcDoc::nil().append(pretty::RcDoc::intersperse( ast.iter().map(|f| f.to_js(package_name)), space(), )); @@ -1105,21 +1100,10 @@ impl ExpressionGenerator { } } -pub(crate) fn get_variable_declaration(variable: &str) -> pretty::RcDoc<'static> { - text("let") - .append(space()) - .append(text(variable)) - .append(space()) - .append(text("=")) - .append(space()) - .append(text("{}")) - .append(text(";")) -} - #[cfg(test)] #[track_caller] pub fn e(f: fastn_js::Ast, s: &str) { - let g = to_js(&[f], false, "foo"); + let g = to_js(&[f], "foo"); println!("got: {}", g); println!("expected: {}", s); assert_eq!(g, s); diff --git a/ftd/src/js/ftd_test_helpers.rs b/ftd/src/js/ftd_test_helpers.rs index 36d5985fa0..e19e17403f 100644 --- a/ftd/src/js/ftd_test_helpers.rs +++ b/ftd/src/js/ftd_test_helpers.rs @@ -99,9 +99,8 @@ fn get_dummy_package_data() -> String { fn p(s: &str, t: &str, fix: bool, manual: bool, script: bool, file_location: &std::path::PathBuf) { let i = interpret_helper("foo", s).unwrap_or_else(|e| panic!("{:?}", e)); let js_ast_data = ftd::js::document_into_js_ast(i); - let js_document_script = fastn_js::to_js(js_ast_data.asts.as_slice(), true, "foo"); - let js_ftd_script = - fastn_js::to_js(ftd::js::default_bag_into_js_ast().as_slice(), false, "foo"); + let js_document_script = fastn_js::to_js(js_ast_data.asts.as_slice(), "foo"); + let js_ftd_script = fastn_js::to_js(ftd::js::default_bag_into_js_ast().as_slice(), "foo"); let dummy_package_data = get_dummy_package_data(); let html_str = { diff --git a/ftd/src/js/mod.rs b/ftd/src/js/mod.rs index 53d7ebb80d..2fcde906df 100644 --- a/ftd/src/js/mod.rs +++ b/ftd/src/js/mod.rs @@ -16,7 +16,7 @@ pub const CODE_DEFAULT_THEME: &str = "fastn-theme.dark"; pub fn all_js_without_test(package_name: &str) -> String { let all_js = fastn_js::all_js_without_test(); - let default_bag_js = fastn_js::to_js(default_bag_into_js_ast().as_slice(), false, package_name); + let default_bag_js = fastn_js::to_js(default_bag_into_js_ast().as_slice(), package_name); format!("{all_js}\n{default_bag_js}") } diff --git a/ftd/t/js/01-basic-module.html b/ftd/t/js/01-basic-module.html index 9501b33469..c1f4f72322 100644 --- a/ftd/t/js/01-basic-module.html +++ b/ftd/t/js/01-basic-module.html @@ -33,9 +33,7 @@ + + + + + + +