1use dioxus::prelude::*;
2
3use hyle::{FieldType, Primitive};
4
5use crate::context::{use_hyle_components, HyleConfig};
6use crate::types::{field_type_key, HyleComponents, HyleFilterField, HyleFilterFieldProps, HyleFiltersState, HyleValueProps};
7
8#[component]
26pub fn FilterField(
27 state: HyleFiltersState,
28 field_key: String,
29) -> Element {
30 let filter_field: Option<HyleFilterField> = state
31 .fields
32 .read()
33 .iter()
34 .find(|f| f.key == field_key)
35 .cloned();
36
37 let Some(ff) = filter_field else {
38 return rsx! {};
39 };
40
41 let key = ff.key.clone();
42 let value = state
43 .form_data
44 .read()
45 .get(&key)
46 .cloned()
47 .unwrap_or_default();
48
49 let set: Callback<String> = {
50 let key = key.clone();
51 Callback::new(move |v: String| state.set_field.call((key.clone(), v)))
52 };
53
54 if let Some(ref render_fn) = ff.render {
56 return render_fn(HyleFilterFieldProps {
57 key: ff.key,
58 label: ff.label,
59 field: ff.field,
60 options: ff.options,
61 value,
62 set,
63 });
64 }
65
66 let props = HyleFilterFieldProps {
68 key: ff.key.clone(),
69 label: ff.label.clone(),
70 field: ff.field.clone(),
71 options: ff.options.clone(),
72 value: value.clone(),
73 set: set.clone(),
74 };
75 if let Some(components) = use_hyle_components() {
76 let type_key = field_type_key(&ff.field.field_type);
77 if let Some(render_fn) = components.filters.get(type_key).copied() {
78 return render_fn(props);
79 }
80 }
81
82 default_input(ff, value, set)
84}
85
86#[component]
90pub fn FormFilterField(
91 state: HyleFiltersState,
92 field_key: String,
93) -> Element {
94 let filter_field: Option<HyleFilterField> = state
95 .fields
96 .read()
97 .iter()
98 .find(|f| f.key == field_key)
99 .cloned();
100
101 let Some(ff) = filter_field else {
102 return rsx! {};
103 };
104
105 let key = ff.key.clone();
106 let value = state
107 .form_data
108 .read()
109 .get(&key)
110 .cloned()
111 .unwrap_or_default();
112
113 let set: Callback<String> = {
114 let key = key.clone();
115 Callback::new(move |v: String| state.set_field.call((key.clone(), v)))
116 };
117
118 if let Some(ref render_fn) = ff.render {
119 return render_fn(HyleFilterFieldProps {
120 key: ff.key, label: ff.label, field: ff.field, options: ff.options, value, set,
121 });
122 }
123
124 form_default_input(ff, value, set)
125}
126
127fn form_default_input(ff: HyleFilterField, value: String, set: Callback<String>) -> Element {
128 match &ff.field.field_type {
129 FieldType::Primitive {
130 primitive: Primitive::Boolean,
131 } => boolean_checkbox(ff.key, ff.label, value, set),
132 _ => default_input(ff, value, set),
133 }
134}
135
136fn default_input(ff: HyleFilterField, value: String, set: Callback<String>) -> Element {
139 match &ff.field.field_type {
140 FieldType::Primitive {
141 primitive: Primitive::Boolean,
142 } => boolean_select(ff.key, ff.label, value, set),
143
144 FieldType::Reference { .. } => match ff.options {
145 Some(options) => reference_select(ff.key, ff.label, value, options, set),
146 None => reference_select_loading(ff.key, ff.label),
147 },
148
149 FieldType::Array { .. } => {
150 match ff.options {
151 Some(options) => checkbox_reference_fieldset(ff.key, ff.label, value, options, ff.display_field_type.clone(), set),
152 None => rsx! {
153 fieldset {
154 legend { "{ff.label}" }
155 span { aria_busy: "true", "Loading…" }
156 }
157 },
158 }
159 }
160
161 _ => text_input(ff, value, set),
162 }
163}
164
165fn boolean_select(name: String, label: String, value: String, set: Callback<String>) -> Element {
166 rsx! {
167 label {
168 "{label}"
169 select {
170 name: "{name}",
171 onchange: move |e| set.call(e.value()),
172 option { value: "", selected: value.is_empty(), "Any" }
173 option { value: "true", selected: value == "true", "Yes" }
174 option { value: "false", selected: value == "false", "No" }
175 }
176 }
177 }
178}
179
180pub(crate) fn boolean_checkbox(name: String, label: String, value: String, set: Callback<String>) -> Element {
181 rsx! {
182 fieldset {
183 legend { "" }
184 label {
185 input {
186 r#type: "checkbox",
187 name: "{name}",
188 checked: value == "true",
189 onchange: move |e| set.call(if e.checked() { "true".to_owned() } else { String::new() }),
190 }
191 "{label}"
192 }
193 }
194 }
195}
196
197fn reference_select_loading(name: String, label: String) -> Element {
198 rsx! {
199 label {
200 "{label}"
201 select { name: "{name}", disabled: true,
202 option { "Loading…" }
203 }
204 }
205 }
206}
207
208fn reference_select(
209 name: String,
210 label: String,
211 value: String,
212 options: Vec<(String, String)>,
213 set: Callback<String>,
214) -> Element {
215 rsx! {
216 label {
217 "{label}"
218 select {
219 name: "{name}",
220 onchange: move |e| set.call(e.value()),
221 option { value: "", selected: value.is_empty(), "All {label}s" }
222 for (id, display) in options {
223 option { key: "{id}", value: "{id}", selected: value == id, "{display}" }
224 }
225 }
226 }
227 }
228}
229
230fn checkbox_reference_fieldset(
231 name: String,
232 label: String,
233 value: String,
234 options: Vec<(String, String)>,
235 display_field_type: Option<FieldType>,
236 set: Callback<String>,
237) -> Element {
238 let selected: Vec<String> = if value.is_empty() {
239 vec![]
240 } else {
241 value.split(',').map(|s| s.trim().to_owned()).collect()
242 };
243
244 let components: Option<HyleComponents> = use_hyle_components();
246 let label_render_fn: Option<fn(HyleValueProps) -> Element> =
247 display_field_type.as_ref().and_then(|ft| {
248 components.as_ref().and_then(|c| {
249 let key = field_type_key(ft);
250 c.values.get(key).copied()
251 })
252 });
253
254 let blueprint = use_context::<HyleConfig>().blueprint;
256
257 rsx! {
258 fieldset {
259 legend { "{label}" }
260 for (id, display) in options {
261 label {
262 key: "{id}",
263 input {
264 r#type: "checkbox",
265 name: "{name}",
266 value: "{id}",
267 checked: selected.contains(&id),
268 onchange: {
269 let id = id.clone();
270 let value = value.clone();
271 let set = set.clone();
272 move |e: Event<FormData>| {
273 let mut current: Vec<String> = if value.is_empty() {
274 vec![]
275 } else {
276 value.split(',').map(|s| s.trim().to_owned()).collect()
277 };
278 if e.checked() {
279 if !current.contains(&id) {
280 current.push(id.clone());
281 }
282 } else {
283 current.retain(|s| s != &id);
284 }
285 set.call(current.join(","));
286 }
287 },
288 }
289 if let Some(render_fn) = label_render_fn {
290 {
291 let ft = display_field_type.clone().unwrap_or(FieldType::Primitive { primitive: Primitive::String });
292 let field = hyle::Field { label: display.clone(), field_type: ft, options: Default::default() };
293 let display_val = hyle::Value::String(display.clone());
294 render_fn(HyleValueProps {
295 key: id.clone(),
296 field,
297 value: display_val,
298 outcome: hyle::Outcome::empty(),
299 model_name: String::new(),
300 blueprint: (*blueprint).clone(),
301 components: components.clone(),
302 })
303 }
304 } else {
305 " {display}"
306 }
307 }
308 }
309 }
310 }
311}
312
313fn text_input(ff: HyleFilterField, value: String, set: Callback<String>) -> Element {
314 let input_type = ff
315 .field
316 .options
317 .input
318 .as_ref()
319 .map(|i| i.kind.clone())
320 .unwrap_or_else(|| "text".to_owned());
321
322 rsx! {
323 label {
324 "{ff.label}"
325 input {
326 r#type: "{input_type}",
327 name: "{ff.key}",
328 placeholder: "{ff.label}",
329 value: "{value}",
330 oninput: move |e| set.call(e.value()),
331 }
332 }
333 }
334}