diff options
Diffstat (limited to 'rocie-macros/src/form/generate.rs')
| -rw-r--r-- | rocie-macros/src/form/generate.rs | 255 |
1 files changed, 206 insertions, 49 deletions
diff --git a/rocie-macros/src/form/generate.rs b/rocie-macros/src/form/generate.rs index 6673ba5..5642e6a 100644 --- a/rocie-macros/src/form/generate.rs +++ b/rocie-macros/src/form/generate.rs @@ -1,7 +1,7 @@ use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use quote::{format_ident, quote}; -use syn::{Ident, parse_macro_input}; +use syn::{Ident, Type, parse_macro_input}; use crate::form::{ParsedChild, ParsedInput}; @@ -10,6 +10,8 @@ pub fn form_impl(item: TokenStream) -> TokenStream { let node_refs = input.children.iter().map(node_ref); let signals = input.children.iter().map(signal); + let reactive_signals = input.children.iter().map(reactive_signal); + let auto_complete_signals = input.children.iter().map(auto_complete_signal); let type_verifications = input.children.iter().map(type_verification); @@ -20,6 +22,11 @@ pub fn form_impl(item: TokenStream) -> TokenStream { let bounds = input.on_submit.inputs; let on_submit_block = input.on_submit.block; + let on_submit_move = if input.on_submit.should_use_move { + quote! {move} + } else { + quote! {} + }; let input_htmls = input.children.iter().map(input_html); @@ -50,11 +57,11 @@ pub fn form_impl(item: TokenStream) -> TokenStream { #( #node_refs - )* - #( + #signals - )* - #( + #reactive_signals + #auto_complete_signals + #type_verifications )* @@ -68,7 +75,7 @@ pub fn form_impl(item: TokenStream) -> TokenStream { #fetch_values )* - let real_on_submit = |Inputs {#(#bounds),*}| #on_submit_block; + let real_on_submit = #on_submit_move |Inputs {#(#bounds),*}| #on_submit_block; real_on_submit( #output_struct_construction ) @@ -76,16 +83,17 @@ pub fn form_impl(item: TokenStream) -> TokenStream { view! { - <form class="flex flex-col contents-start m-2 g-2" on:submit=on_submit> + <form class="relative flex flex-col contents-start m-2 g-2" on:submit=on_submit> #( #input_htmls )* - <div class="static"> + <div class="sticky bottom-2 left-0 flex flex-row contents-start"> + <div class="w-5/6" /> <input type="submit" value="Submit" - class="absolute bottom-0 right-0 h-20 w-20 rounded-lg bg-green-300 m-2" + class="pr-2 pl-2 rounded-lg bg-green-300 m-2" /> </div> </form> @@ -112,15 +120,20 @@ fn node_ref_name(name: &Ident) -> Ident { } fn node_ref(child: &ParsedChild) -> TokenStream2 { - let (name, ty) = match child { - ParsedChild::Input { name, .. } => (name, quote! {Input}), - ParsedChild::Select { name, .. } => (name, quote! {Select}), - }; + if let ParsedChild::Show { children, .. } = child { + children.iter().map(node_ref).collect() + } else { + let (name, ty) = match child { + ParsedChild::Input { name, .. } => (name, quote! {Input}), + ParsedChild::Select { name, .. } => (name, quote! {Select}), + ParsedChild::Show { .. } => unreachable!("Filtered out before"), + }; - let node_ref_name = node_ref_name(name); + let node_ref_name = node_ref_name(name); - quote! { - let #node_ref_name: NodeRef<#ty> = NodeRef::new(); + quote! { + let #node_ref_name: NodeRef<#ty> = NodeRef::new(); + } } } @@ -137,42 +150,152 @@ fn signal(child: &ParsedChild) -> TokenStream2 { } } ParsedChild::Select { .. } => quote! {}, + ParsedChild::Show { children, .. } => children.iter().map(signal).collect(), } } -fn type_verification(child: &ParsedChild) -> TokenStream2 { + +fn signal_auto_complete_names(name: &Ident) -> (Ident, Ident) { + ( + format_ident!("{name}_auto_complete_get"), + format_ident!("{name}_auto_complete_set"), + ) +} +fn auto_complete_signal(child: &ParsedChild) -> TokenStream2 { match child { - ParsedChild::Input { .. } => quote! {}, - ParsedChild::Select { - rust_type, options, .. + ParsedChild::Input { + name, + reactive, + auto_complete, + .. } => { - let names: Vec<_> = options - .0 - .iter() - .enumerate() - .map(|(index, _)| format_ident!("name_{index}")) - .collect(); - let values: Vec<_> = options.0.iter().map(|option| option.1.clone()).collect(); + if let Some(auto_complete) = auto_complete { + let (signal_auto_complete_get, _) = signal_auto_complete_names(name); + + let reactive = reactive + .as_ref() + .expect("Must be some, if auto_complete is some"); + + quote! { + let #signal_auto_complete_get = + leptos::prelude::LocalResource::new( + move || #auto_complete(#reactive()) + ); + } + } else { + quote! {} + } + } + ParsedChild::Select { .. } => quote! {}, + ParsedChild::Show { children, .. } => children.iter().map(auto_complete_signal).collect(), + } +} +fn signal_reactive_base_names(name: &Ident) -> (Ident, Ident) { + ( + format_ident!("{name}_reactive_base_get"), + format_ident!("{name}_reactive_base_set"), + ) +} +fn reactive_signal(child: &ParsedChild) -> TokenStream2 { + match child { + ParsedChild::Input { + name, + reactive, + rust_type, + .. + } => { + if let Some(reactive) = reactive { + let (signal_reactive_base_get, signal_reactive_base_set) = + signal_reactive_base_names(name); + + quote! { + let (#signal_reactive_base_get, #signal_reactive_base_set) = signal(None); + #reactive = move || { + { + let output: Option<String> = #signal_reactive_base_get.get(); + + if let Some(output) = output { + let fin: Result<#rust_type, leptos::error::Error> = output + .parse() + .map_err(Into::<leptos::error::Error>::into); + + match fin { + Ok(ok) => { + Some(ok) + }, + Err(err) => { + // Reset the signal + None + } + } + } else { + None + } + } + }; + } + } else { + quote! {} + } + } + ParsedChild::Select { .. } => quote! {}, + ParsedChild::Show { children, .. } => children.iter().map(reactive_signal).collect(), + } +} + +fn type_verification(child: &ParsedChild) -> TokenStream2 { + match child { + ParsedChild::Input { .. } => { + quote! {} + } + ParsedChild::Select { name, options, .. } => { + let name = format_ident!("_select_val_{name}"); quote! { { - #( - let #names: #rust_type = #values; - )* + let #name: + leptos::prelude::LocalResource< + Result< + // TODO: Use #rust_type instead of the second String <2025-10-21> + Vec<(String, String)>, + leptos::error::Error + > + > = #options; } } } + ParsedChild::Show { when: _, children } => { + let base = children + .iter() + .map(type_verification) + .collect::<TokenStream2>(); + + quote! { + #base + } + } + } +} + +fn get_names(input: &ParsedChild) -> Vec<&Ident> { + match input { + ParsedChild::Input { name, .. } => vec![name], + ParsedChild::Select { name, .. } => vec![name], + ParsedChild::Show { children, .. } => children.iter().flat_map(get_names).collect(), } } fn output_struct_definition(children: &[ParsedChild]) -> TokenStream2 { - let names = children.iter().map(|child| match child { - ParsedChild::Input { name, .. } => name, - ParsedChild::Select { name, .. } => name, - }); - let rust_types = children.iter().map(|child| match child { - ParsedChild::Input { rust_type, .. } => rust_type, - ParsedChild::Select { rust_type, .. } => rust_type, - }); + fn get_rust_types(input: &ParsedChild) -> Vec<&Type> { + match input { + ParsedChild::Input { rust_type, .. } => vec![rust_type], + ParsedChild::Select { rust_type, .. } => vec![rust_type], + ParsedChild::Show { children, .. } => { + children.iter().flat_map(get_rust_types).collect() + } + } + } + let names = children.iter().flat_map(get_names); + let rust_types = children.iter().flat_map(get_rust_types); quote! { struct Inputs { @@ -183,10 +306,7 @@ fn output_struct_definition(children: &[ParsedChild]) -> TokenStream2 { } } fn output_struct_construction(children: &[ParsedChild]) -> TokenStream2 { - let names = children.iter().map(|child| match child { - ParsedChild::Input { name, .. } => name, - ParsedChild::Select { name, .. } => name, - }); + let names = children.iter().flat_map(get_names); quote! { Inputs { @@ -270,6 +390,9 @@ fn fetch_value(child: &ParsedChild) -> TokenStream2 { }; } } + ParsedChild::Show { children, .. } => { + children.iter().map(fetch_value).collect::<TokenStream2>() + } } } @@ -280,13 +403,39 @@ fn input_html(child: &ParsedChild) -> TokenStream2 { label, rust_type, html_type, + reactive, + auto_complete, .. } => { let node_ref_name = node_ref_name(name); let (signal_name_get, _) = signal_names(name); + let reactive = { + if reactive.is_some() { + let (_, signal_reactive_set) = signal_reactive_base_names(name); + + quote! { + reactive=Some(#signal_reactive_set) + } + } else { + quote! {} + } + }; + + let auto_complete = { + if auto_complete.is_some() { + let (signal_auto_complete_get, _) = signal_auto_complete_names(name); + + quote! { + auto_complete=Some(#signal_auto_complete_get) + } + } else { + quote! {} + } + }; quote! { - <InputPlaceholder input_type=#html_type label=#label node_ref=#node_ref_name /> + <InputPlaceholder #reactive #auto_complete input_type=#html_type label=#label node_ref=#node_ref_name /> + <Show when=move || #signal_name_get.get().is_some() fallback=|| () @@ -308,14 +457,22 @@ fn input_html(child: &ParsedChild) -> TokenStream2 { .. } => { let node_ref_name = node_ref_name(name); - let options = options.0.iter().map(|(lit, expr)| { - quote! { - (#lit, #expr.to_string()) - } - }); quote! { - <SelectPlaceholder label=#label node_ref=#node_ref_name options=vec![#(#options),*] /> + <SelectPlaceholder label=#label node_ref=#node_ref_name options=#options /> + } + } + ParsedChild::Show { when, children } => { + let rendered_children = children.iter().map(input_html).collect::<Vec<_>>(); + + quote! { + <Show + when=#when + > + #( + #rendered_children + )* + </Show> } } } |
