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, false)
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, true),
133 }
134}
135
136fn default_input(ff: HyleFilterField, value: String, set: Callback<String>, is_form: bool) -> 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 let is_textarea = is_form && ff.field.options.input.as_ref()
152 .map(|i| i.kind == "textarea")
153 .unwrap_or(false);
154
155 if is_textarea {
156 let rows = ff.field.options.input.as_ref()
157 .and_then(|i| i.props.get("rows"))
158 .and_then(|v| v.as_str())
159 .unwrap_or("4");
160 return rsx! {
161 label {
162 "{ff.label}"
163 textarea {
164 name: "{ff.key}",
165 rows: "{rows}",
166 value: "{value}",
167 oninput: move |e| set.call(e.value()),
168 placeholder: "One type per line (e.g., folk\\nrock)",
169 }
170 }
171 };
172 }
173
174 match ff.options {
175 Some(options) => checkbox_reference_fieldset(ff.key, ff.label, value, options, ff.display_field_type.clone(), set),
176 None => rsx! {
177 fieldset {
178 legend { "{ff.label}" }
179 span { aria_busy: "true", "Loading…" }
180 }
181 },
182 }
183 }
184
185 _ => text_input(ff, value, set),
186 }
187}
188
189fn boolean_select(name: String, label: String, value: String, set: Callback<String>) -> Element {
190 rsx! {
191 label {
192 "{label}"
193 select {
194 name: "{name}",
195 onchange: move |e| set.call(e.value()),
196 option { value: "", selected: value.is_empty(), "Any" }
197 option { value: "true", selected: value == "true", "Yes" }
198 option { value: "false", selected: value == "false", "No" }
199 }
200 }
201 }
202}
203
204pub(crate) fn boolean_checkbox(name: String, label: String, value: String, set: Callback<String>) -> Element {
205 rsx! {
206 fieldset {
207 legend { "" }
208 label {
209 input {
210 r#type: "checkbox",
211 name: "{name}",
212 checked: value == "true",
213 onchange: move |e| set.call(if e.checked() { "true".to_owned() } else { String::new() }),
214 }
215 "{label}"
216 }
217 }
218 }
219}
220
221fn reference_select_loading(name: String, label: String) -> Element {
222 rsx! {
223 label {
224 "{label}"
225 select { name: "{name}", disabled: true,
226 option { "Loading…" }
227 }
228 }
229 }
230}
231
232fn reference_select(
233 name: String,
234 label: String,
235 value: String,
236 options: Vec<(String, String)>,
237 set: Callback<String>,
238) -> Element {
239 rsx! {
240 label {
241 "{label}"
242 select {
243 name: "{name}",
244 onchange: move |e| set.call(e.value()),
245 option { value: "", selected: value.is_empty(), "All {label}s" }
246 for (id, display) in options {
247 option { key: "{id}", value: "{id}", selected: value == id, "{display}" }
248 }
249 }
250 }
251 }
252}
253
254fn checkbox_reference_fieldset(
255 name: String,
256 label: String,
257 value: String,
258 options: Vec<(String, String)>,
259 display_field_type: Option<FieldType>,
260 set: Callback<String>,
261) -> Element {
262 let selected: Vec<String> = if value.is_empty() {
263 vec![]
264 } else {
265 value.split(',').map(|s| s.trim().to_owned()).collect()
266 };
267
268 let components: Option<HyleComponents> = use_hyle_components();
270 let label_render_fn: Option<fn(HyleValueProps) -> Element> =
271 display_field_type.as_ref().and_then(|ft| {
272 components.as_ref().and_then(|c| {
273 let key = field_type_key(ft);
274 c.values.get(key).copied()
275 })
276 });
277
278 let blueprint = use_context::<HyleConfig>().blueprint;
280
281 rsx! {
282 fieldset {
283 legend { "{label}" }
284 for (id, display) in options {
285 label {
286 key: "{id}",
287 input {
288 r#type: "checkbox",
289 name: "{name}",
290 value: "{id}",
291 checked: selected.contains(&id),
292 onchange: {
293 let id = id.clone();
294 let value = value.clone();
295 let set = set.clone();
296 move |e: Event<FormData>| {
297 let mut current: Vec<String> = if value.is_empty() {
298 vec![]
299 } else {
300 value.split(',').map(|s| s.trim().to_owned()).collect()
301 };
302 if e.checked() {
303 if !current.contains(&id) {
304 current.push(id.clone());
305 }
306 } else {
307 current.retain(|s| s != &id);
308 }
309 set.call(current.join(","));
310 }
311 },
312 }
313 if let Some(render_fn) = label_render_fn {
314 {
315 let ft = display_field_type.clone().unwrap_or(FieldType::Primitive { primitive: Primitive::String });
316 let field = hyle::Field { label: display.clone(), field_type: ft, options: Default::default() };
317 let display_val = hyle::Value::String(display.clone());
318 render_fn(HyleValueProps {
319 key: id.clone(),
320 field,
321 value: display_val,
322 outcome: hyle::Outcome::empty(),
323 model_name: String::new(),
324 blueprint: (*blueprint).clone(),
325 components: components.clone(),
326 })
327 }
328 } else {
329 " {display}"
330 }
331 }
332 }
333 }
334 }
335}
336
337fn text_input(ff: HyleFilterField, value: String, set: Callback<String>) -> Element {
338 let input_type = ff
339 .field
340 .options
341 .input
342 .as_ref()
343 .map(|i| i.kind.clone())
344 .unwrap_or_else(|| "text".to_owned());
345
346 rsx! {
347 label {
348 "{ff.label}"
349 input {
350 r#type: "{input_type}",
351 name: "{ff.key}",
352 placeholder: "{ff.label}",
353 value: "{value}",
354 oninput: move |e| set.call(e.value()),
355 }
356 }
357 }
358}