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