feat: implement working MultiEntry component

This MultiEntry (Rewrite) is capable of storing and sending values through the server functions, as well as dynamically updating the master list as values are changed.
This commit is contained in:
alphafetus committed 2025-10-16 11:51:27 -04:00
1 parent 6b1e2174b6
commit 86bd471c1e
9 files changed
+369 -32

No files matched your search

+114 -12
View File
@@ -1,18 +1,120 @@
use std::collections::HashMap;
use leptos::{view, IntoView};
use leptos::either::EitherOf5;
use leptos::prelude::RwSignal;
use serde::{Serialize, Deserialize};
use crate::components::{CheckboxList, OutlinedTextField, RadioList, Row, Select, ValueType};
/// Defines a series of options that allow a user to define a form.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum InputType{
/// Represents a text input. Requires a `String` key. The entered value will be returned as a
/// single `String` value.
Text,
/// Represents a number input. Requires a `String` key. The entered value will be returned as a
/// single `i32` value.
Number,
/// Represents a radio input. Requires a `String` key and a series of options. The selected
/// value will be returned as a single `String` value.
Radio(Vec<String>),
/// Represents a checkbox input. Requires a `String` key and a series of options. The selected
/// value will be returned as a `Vec<String>`.
Checkbox(Vec<String>)
/// Represents a text input. Requires a `String` key, `String` label, and `String` placeholder.
/// The entered value will be returned as a single `String` value.
Text(String, String, String),
/// Represents a number input. Requires a `String` key, `String` label, and a `String` placeholder.
/// The entered value will be returned as a `String`, and will be parseable into any number type
/// (i.e. an `i32`, `i64`, etc)
Number(String, String, String),
/// Represents a radio input. Requires a `String` key, `String` label, and a series of `String`
/// options. The selected value will be returned as a `String`.
Radio(String, String, Vec<String>),
/// Represents a checkbox input. Requires a `String` key, `String` label, and a series of `String`
/// options. The selected values will be returned as a `Vec<String>`.
Checkbox(String, String, Vec<String>),
/// Represents a dropdown input. Requires a `String` key, `String` label, and a series
/// of `String` options. The selected value will be returned as a `String`.
Select(String, String, Vec<String>)
}
impl InputType {
pub fn into_view(self, data_map: RwSignal<HashMap<String, ValueType>>) -> impl IntoView {
view! {
{match self {
InputType::Text(member, label, placeholder) =>
EitherOf5::A(view! {
<Row>
<OutlinedTextField
label = label.clone()
placeholder = placeholder.clone()
data_member = member.clone()
data_map = data_map
/>
</Row>
}),
InputType::Number(member, label, placeholder) =>
EitherOf5::B(view! {
<OutlinedTextField
label = label.clone()
placeholder = placeholder.clone()
data_member = member.clone()
data_map = data_map
/>
}),
InputType::Checkbox(member, label, options) =>
EitherOf5::C(view! {
<CheckboxList
label = label.clone()
items = options.clone()
data_member = member.clone()
data_map = data_map
/>
}),
InputType::Radio(member, label, options) =>
EitherOf5::D(view! {
<RadioList
label = label.clone()
data_member = member.clone()
data_map = data_map
items = options.clone()
/>
}),
InputType::Select(member, label, options) =>
EitherOf5::E(view! {
<Select
label = label.clone()
value_list = options.clone()
data_member = member.clone()
data_map = data_map
/>
})
}}
}
}
pub fn into_view_rows(self, data_map: RwSignal<HashMap<String, ValueType>>) -> impl IntoView {
view! {
<Row>
{self.into_view(data_map)}
</Row>
}
}
}
/// Shorthand macro for creating `InputType` enums.
///
/// Example usage:
/// ```
/// // To create an InputType::Text
/// let text_input = input!(Text, "member", "label", "placeholder");
///
/// // To create an InputType::Select
/// let select_input = input!(Select, "member", "label", ["option1", "option2"]);
/// ```
#[macro_export]
macro_rules! input {
($variant:ident, $key:expr, $label:expr, [$($option:expr),* $(,)?]) => {
$crate::common::InputType::$variant(
$key.to_string(),
$label.to_string(),
vec![$($option.to_string()), *]
)
};
($variant:ident, $key:expr, $label:expr, $placeholder:expr) => {
$crate::common::InputType::$variant(
$key.to_string(),
$label.to_string(),
$placeholder.to_string()
)
};
}
+25 -3
View File
@@ -1,9 +1,14 @@
use std::collections::HashMap;
use serde::{Serialize, Deserialize};
use std::fmt::Display;
/// Defines a set of possible input types. They allow for the type and value of every data member
/// to be stored within a single `HashMap` or `Vec`. This means that even though all the data is
/// created as a single type, the values are still able to be type safe.
///
/// A note: other structs cannot be stored in this enum, however it was designed to work with
/// a `HashMap` of more `ValueType` enums. There are numerous ways to convert a struct into a
/// `HashMap`, specifically storing all the struct's fields as `String` keys in the map.
#[derive(PartialEq, Clone, Serialize, Deserialize, Debug)]
pub enum ValueType {
/// Indicates a `String` value.
@@ -12,10 +17,12 @@ pub enum ValueType {
Number(Option<String>),
/// Indicates a `Vec<T>`, where `T` can be any other `ValueType`.
List(Option<Vec<ValueType>>),
/// Indicates a `HashMap<String, ValueType>`.
Map(Option<HashMap<String, ValueType>>),
}
impl ValueType {
/// Returns true if this is a `String`.
/// Returns true if this is a `String`, otherwise false.
pub fn is_string(&self) -> bool {
if let ValueType::String(_) = self { true } else { false }
}
@@ -29,7 +36,7 @@ impl ValueType {
}
}
/// Returns true if this is a `Number`.
/// Returns true if this is a `Number`, otherwise false.
pub fn is_number(&self) -> bool {
if let ValueType::Number(_) = self { true } else { false }
}
@@ -43,7 +50,7 @@ impl ValueType {
}
}
/// Returns true if this is a `List`.
/// Returns true if this is a `List`, otherwise false.
pub fn is_list(&self) -> bool {
if let ValueType::List(_) = self { true } else { false }
}
@@ -57,6 +64,20 @@ impl ValueType {
Err(self.clone())
}
}
/// Returns true if this is a `Map`, otherwise false.
pub fn is_map(&self) -> bool {
if let ValueType::Map(_) = self { true } else { false }
}
/// Attempts to get this value as a `HashMap<String, ValueType>`.
pub fn as_map(&self) -> Result<Option<HashMap<String, ValueType>>, Self> {
if let ValueType::Map(v) = self {
Ok(v.clone())
} else {
Err(self.clone())
}
}
}
impl Default for ValueType {
@@ -71,6 +92,7 @@ impl Display for ValueType {
ValueType::String(s) => s.clone().unwrap_or("".to_owned()),
ValueType::Number(n) => n.clone().unwrap_or("".to_owned()),
ValueType::List(l) => format!("{l:?}"),
ValueType::Map(m) => format!("{m:?}"),
};
write!(f, "{}", str)
}