1use leptos::*;
4use std::rc::Rc;
5
6pub trait OnError<I: 'static, T: Clone + 'static, IV: IntoView + 'static>:
7 Fn(ServerFnError, Action<I, Result<T, ServerFnError>>) -> IV + 'static
8{
9}
10
11pub trait OnSuccess<I: 'static, T: Clone + 'static, IV: IntoView + 'static>:
12 Fn(T, Action<I, Result<T, ServerFnError>>) -> IV + 'static
13{
14}
15
16impl<I: 'static, T: Clone + 'static, IV: IntoView + 'static, F> OnError<I, T, IV> for F where
17 F: Fn(ServerFnError, Action<I, Result<T, ServerFnError>>) -> IV + 'static
18{
19}
20impl<I: 'static, T: Clone + 'static, IV: IntoView + 'static, F> OnSuccess<I, T, IV> for F where
21 F: Fn(T, Action<I, Result<T, ServerFnError>>) -> IV + 'static
22{
23}
24
25pub struct LeptosFormChildren(pub Rc<dyn Fn() -> View + 'static>);
26
27impl<T: IntoView, F: Fn() -> T + 'static> From<F> for LeptosFormChildren {
28 fn from(f: F) -> Self {
29 Self(Rc::new(move || <T as IntoView>::into_view(f())))
30 }
31}
32
33#[component]
34pub fn FormSubmissionHandler<IV1: IntoView + 'static, IV2: IntoView + 'static, I: 'static, T: Clone + 'static>(
35 action: Action<I, Result<T, ServerFnError>>,
36 #[prop(optional)] on_error: Option<Rc<dyn OnError<I, T, IV2>>>,
37 #[prop(optional)] on_success: Option<Rc<dyn OnSuccess<I, T, IV1>>>,
38 #[allow(unused_variables)]
39 #[prop(optional)]
40 error_view_ty: Option<std::marker::PhantomData<IV2>>,
41 #[allow(unused_variables)]
42 #[prop(optional)]
43 success_view_ty: Option<std::marker::PhantomData<IV1>>,
44) -> impl IntoView {
45 view! {{move || match action.pending().get() {
46 true => view! { <div>"Loading..."</div> }.into_view(),
47 false => match action.value().get() {
48 Some(Ok(ok)) => match on_success.clone() {
49 Some(on_success) => on_success(ok, action).into_view(),
50 None => View::default(),
51 },
52 Some(Err(err)) => match on_error.clone() {
53 Some(on_error) => on_error(err, action).into_view(),
54 None => view! {
55 <div>
56 {move || match err.clone() {
57 ServerFnError::Request(err) => err,
58 ServerFnError::ServerError(err) => err,
59 _ => "Internal Error".to_string(),
60 }}
61 </div>
62 }.into_view(),
63 },
64 None => View::default(),
65 }
66 }}}
67}
68
69pub type StyleSignal = Rc<dyn Fn() -> Option<&'static str>>;
71
72#[component]
73pub fn MaterialIcon(
74 d: &'static str,
75 #[prop(optional_no_strip, into)] id: Option<Oco<'static, str>>,
76 #[prop(optional_no_strip, into)] class: Option<Oco<'static, str>>,
77 #[prop(optional_no_strip, into)] cursor: Option<StyleSignal>,
78 #[prop(optional_no_strip, into)] height: Option<usize>,
79 #[prop(optional_no_strip, into)] opacity: Option<StyleSignal>,
80 #[prop(optional_no_strip, into)] style: Option<Oco<'static, str>>,
81 #[prop(optional_no_strip, into)] width: Option<usize>,
82) -> impl IntoView {
83 let transform = svg_transform(24, 24, height, width);
84 let style = match (style, transform) {
85 (Some(style), Some(transform)) => Some(format!("{transform} {style}")),
86 (Some(style), None) => Some(style.to_string()),
87 (None, Some(transform)) => Some(transform),
88 (None, None) => None,
89 };
90 let cursor = move || cursor.clone().and_then(|x| x());
91 let opacity = move || opacity.clone().and_then(|x| x());
92 view! {
93 <svg
94 id=id
95 class=class
96 xmlns="http://www.w3.org/2000/svg"
97 height="24"
98 width="24"
99 viewBox="0 -960 960 960"
100 fill="currentColor"
101 style:cursor=cursor
102 style:opacity=opacity
103 style=style
104 >
105 <path d={d} />
106 </svg>
107 }
108}
109
110fn svg_transform(
111 default_height: usize,
112 default_width: usize,
113 height: Option<usize>,
114 width: Option<usize>,
115) -> Option<String> {
116 let height = height.unwrap_or(default_height);
117 let width = width.unwrap_or(default_width);
118
119 let xscale = (width != default_width).then_some(width as f32 / default_width as f32);
120 let yscale = (height != default_height).then_some(height as f32 / default_height as f32);
121 if xscale.is_none() && yscale.is_none() {
122 return None;
123 }
124
125 let xtranslate = xscale.map(|xscale| (xscale - 1.) * default_width as f32 / 2.);
126 let ytranslate = yscale.map(|yscale| (yscale - 1.) * default_height as f32 / 2.);
127
128 let xscale = xscale.map(|val| val.to_string()).unwrap_or_default();
129 let yscale = yscale.map(|val| val.to_string()).unwrap_or_default();
130 let xtranslate = xtranslate.map(|val| val.to_string()).unwrap_or_default();
131 let ytranslate = ytranslate.map(|val| val.to_string()).unwrap_or_default();
132
133 Some(format!(
134 "transform: translate({xtranslate} {ytranslate}) scale({xscale} {yscale});"
135 ))
136}
137
138#[component]
139pub fn MaterialClose(
140 #[prop(optional_no_strip, into)] id: Option<Oco<'static, str>>,
141 #[prop(optional_no_strip, into)] class: Option<Oco<'static, str>>,
142 #[prop(optional_no_strip, into)] cursor: Option<StyleSignal>,
143 #[prop(optional_no_strip, into)] height: Option<usize>,
144 #[prop(optional_no_strip, into)] opacity: Option<StyleSignal>,
145 #[prop(optional_no_strip, into)] style: Option<Oco<'static, str>>,
146 #[prop(optional_no_strip, into)] width: Option<usize>,
147) -> impl IntoView {
148 view! {
149 <MaterialIcon
150 id=id
151 class=class
152 cursor=cursor
153 d="m256-200-56-56 224-224-224-224 56-56 224 224 224-224 56 56-224 224 224 224-56 56-224-224-224 224Z"
154 height=height
155 opacity=opacity
156 style=style
157 width=width
158 />
159 }
160}