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