chore: implement basic logic to show a text field in the MultiEntry component
This logic does not support any info types other than a String, represented by an OutlinedTextField. To support more, this will likely need to completely overhaul every input component so that they accept the ValueType as a primary argument.
This commit is contained in:
1 parent
4fc01acd50
commit
0d4bbc9e70
9 files changed
+241
-75
No files matched your search
@@ -0,0 +1,18 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
/// 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>)
|
||||
}
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
mod student_info;
|
||||
mod user;
|
||||
mod form_schema;
|
||||
|
||||
pub use self::{student_info::*, user::*};
|
||||
pub use self::{student_info::*, user::*, form_schema::*};
|
||||
@@ -0,0 +1,11 @@
|
||||
pub mod multi_entry_data;
|
||||
pub mod value_type;
|
||||
pub mod multi_entry;
|
||||
mod multi_entry_member;
|
||||
|
||||
pub use self::{
|
||||
value_type::*,
|
||||
multi_entry_data::*,
|
||||
multi_entry::*,
|
||||
multi_entry_member::*,
|
||||
};
|
||||
@@ -1,75 +1,27 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::components::ActionButton;
|
||||
use std::sync::Arc;
|
||||
use leptos::either::either;
|
||||
use crate::components::{ActionButton, OutlinedTextField};
|
||||
use leptos::prelude::*;
|
||||
use macros::Reactive;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
use crate::common::InputType;
|
||||
use crate::components::ValueType;
|
||||
use super::{multi_entry_data::*, multi_entry_member::*};
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, Reactive, Default)]
|
||||
pub struct MultiEntryData {
|
||||
pub id: Uuid,
|
||||
pub data: HashMap<String, ValueType>,
|
||||
}
|
||||
|
||||
impl MultiEntryData {
|
||||
/// Creates a new MultiEntryData struct.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
id: Uuid::new_v4(),
|
||||
data: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Contains all information required to display an entry in the MultiEntry component.
|
||||
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||
pub struct MultiEntryMember {
|
||||
/// The name of the member, used for display only.
|
||||
pub display_name: String,
|
||||
/// The actual name of the member, used to get the data from an Entry struct.
|
||||
pub member_name: String,
|
||||
}
|
||||
|
||||
impl MultiEntryMember {
|
||||
/// Creates a new MultiEntryMember struct.
|
||||
pub fn _new() -> Self {
|
||||
Self {
|
||||
display_name: String::new(),
|
||||
member_name: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new MultiEntryMember struct with the specified information.
|
||||
pub fn _new_with_data(display_name: String, member_name: String) -> Self {
|
||||
Self {
|
||||
display_name,
|
||||
member_name,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new MultiEntryMember struct with the specified information. Uses string slices.
|
||||
pub fn from_str(display_name: &str, member_name: &str) -> Self {
|
||||
Self {
|
||||
display_name: display_name.into(),
|
||||
member_name: member_name.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||
pub enum ValueType {
|
||||
String(String),
|
||||
Number(i32),
|
||||
}
|
||||
|
||||
impl ValueType {
|
||||
pub fn as_string(&self) -> String {
|
||||
match self {
|
||||
ValueType::String(s) => s.clone(),
|
||||
ValueType::Number(n) => n.to_string(),
|
||||
}
|
||||
}
|
||||
fn render_entry_component(entry_data: MultiEntryData, member_info: MultiEntryMember, label: String) -> impl IntoView {
|
||||
// Parse the multientry members into the correct components
|
||||
let data = entry_data.data.get(&member_info.member_name);
|
||||
let reactive = RwSignal::new(data.unwrap().as_string().unwrap());
|
||||
let temp = either!(member_info.input_type,
|
||||
InputType::Text => view! {
|
||||
<OutlinedTextField
|
||||
label=member_info.display_name
|
||||
placeholder="testing"
|
||||
value=reactive
|
||||
/>
|
||||
},
|
||||
_ => view! {}
|
||||
);
|
||||
|
||||
temp.into_view()
|
||||
}
|
||||
|
||||
/// Represents a single entry in a MultiEntry component.
|
||||
@@ -86,30 +38,54 @@ pub fn Entry(
|
||||
/// The secondary display member for this entry.
|
||||
#[prop()]
|
||||
info_member: Signal<MultiEntryMember>,
|
||||
/// The schema for all inputs that should be used, and what `MultiEntryMember` info is associated
|
||||
/// with them.
|
||||
#[prop()]
|
||||
schema: Arc<Vec<MultiEntryMember>>
|
||||
) -> impl IntoView {
|
||||
let name_data = entry_data.data.get(&name_member.get().member_name).unwrap();
|
||||
let info_data = entry_data.data.get(&info_member.get().member_name).unwrap();
|
||||
|
||||
let schema = Arc::clone(&schema);
|
||||
|
||||
view! {
|
||||
// This div MUST have the same spacing CSS as the second-level header div
|
||||
<div class="flex rounded-sm transition-shadow shadow-sm hover:shadow-lg/30 p-3 m-1">
|
||||
<span class="flex-1">{name_data.as_string()}</span>
|
||||
<span class="flex-1">{info_data.as_string()}</span>
|
||||
<span class="flex-1">{name_data.to_string()}</span>
|
||||
<span class="flex-1">{info_data.to_string()}</span>
|
||||
</div>
|
||||
{schema
|
||||
.iter()
|
||||
.map(|input_type| {
|
||||
let data = entry_data.clone();
|
||||
let input = input_type.clone();
|
||||
view! {
|
||||
<div>{render_entry_component(data, input, "test".to_owned())}</div>
|
||||
}
|
||||
}).collect::<Vec<_>>()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows users to enter more open-ended information, and generates a `Vec<MultiDataEntry>` filled
|
||||
/// with the user's entered information. The schema for this data must be defined by this component
|
||||
/// such that it is constructable using a series of `String` keys, where each key stores a single
|
||||
/// `ValueType` enum.
|
||||
#[component]
|
||||
pub fn MultiEntry(
|
||||
/// The list of entries to use.
|
||||
#[prop()]
|
||||
entries: RwSignal<Vec<MultiEntryData>>,
|
||||
/// the main display member for each entry object.
|
||||
/// The main display member for each entry object.
|
||||
#[prop()]
|
||||
name_member: Signal<MultiEntryMember>,
|
||||
/// The secondary display member for each entry object.
|
||||
#[prop()]
|
||||
info_member: Signal<MultiEntryMember>,
|
||||
/// The schema of the components. Determines what kind of data is stored and where.
|
||||
/// The `name_member` and `info_member` should be included in this list.
|
||||
#[prop(optional)]
|
||||
schema: Vec<MultiEntryMember>
|
||||
) -> impl IntoView {
|
||||
// Creates an entry in the given entries list.
|
||||
let create_entry = move |_| {
|
||||
@@ -124,6 +100,8 @@ pub fn MultiEntry(
|
||||
);
|
||||
entries.update(|entry_vec| entry_vec.push(new_entry));
|
||||
};
|
||||
|
||||
let rc_schema = Arc::new(schema);
|
||||
|
||||
view! {
|
||||
<div class="flex-auto p-2 m-5">
|
||||
@@ -145,11 +123,13 @@ pub fn MultiEntry(
|
||||
each=move || entries.get()
|
||||
key=|entry: &MultiEntryData| entry.id
|
||||
children=move |entry| {
|
||||
let schema = Arc::clone(&rc_schema);
|
||||
view! {
|
||||
<Entry
|
||||
entry_data=entry.clone()
|
||||
name_member=name_member
|
||||
info_member=info_member
|
||||
schema=schema
|
||||
/>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
use std::collections::HashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
use macros::Reactive;
|
||||
use crate::components::ValueType;
|
||||
|
||||
/// Stores all data for the `MultiEntry` component.
|
||||
///
|
||||
/// All values are stored within the `HashMap`. These entries only contain the value and
|
||||
/// the type of the value itself. Each piece of data also has a unique ID, which allows for simpler
|
||||
/// dynamic rendering.`data`
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, Reactive, Default)]
|
||||
pub struct MultiEntryData {
|
||||
/// The data's unique ID.
|
||||
pub id: Uuid,
|
||||
/// The pertinent data for this struct. This may be variable - each `MultiEntry` component
|
||||
/// may specify its own schema and way of storing this data. The only limitation will be the
|
||||
/// types of data that can be stored - they must be easily parsable by a standard HTML input.
|
||||
pub data: HashMap<String, ValueType>,
|
||||
}
|
||||
|
||||
impl MultiEntryData {
|
||||
/// Creates a new MultiEntryData struct.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
id: Uuid::new_v4(),
|
||||
data: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::common::InputType;
|
||||
|
||||
/// Contains all information required to display an entry in the MultiEntry component.
|
||||
///
|
||||
/// NOTE: This does not define a schema for the data stored by the component, only the data that the
|
||||
/// component displays as "identifying information."
|
||||
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||
pub struct MultiEntryMember {
|
||||
/// The name of the member, used for display only.
|
||||
pub display_name: String,
|
||||
/// The actual name of the member, used to get the data from an Entry struct.
|
||||
pub member_name: String,
|
||||
/// The type of input that should be used to display this member.
|
||||
pub input_type: InputType
|
||||
}
|
||||
|
||||
impl MultiEntryMember {
|
||||
/// Creates a new MultiEntryMember struct.
|
||||
pub fn _new() -> Self {
|
||||
Self {
|
||||
display_name: String::new(),
|
||||
member_name: String::new(),
|
||||
input_type: InputType::Text,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new MultiEntryMember struct with the specified information.
|
||||
pub fn _new_with_data(display_name: String, member_name: String, input_type: InputType) -> Self {
|
||||
Self {
|
||||
display_name,
|
||||
member_name,
|
||||
input_type
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new MultiEntryMember struct with the specified information. Uses string slices.
|
||||
pub fn from_str(display_name: &str, member_name: &str) -> Self {
|
||||
Self {
|
||||
display_name: display_name.into(),
|
||||
member_name: member_name.into(),
|
||||
input_type: InputType::Text
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
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.
|
||||
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||
pub enum ValueType {
|
||||
/// Indicates a `String` value.
|
||||
String(String),
|
||||
/// Indicates an `i32` value.
|
||||
Number(i32),
|
||||
/// Indicates a `Vec<T>`, where `T` can be any other `ValueType`.
|
||||
List(Vec<ValueType>),
|
||||
}
|
||||
|
||||
impl ValueType {
|
||||
/// Returns true if this is a `String`.
|
||||
pub fn is_string(&self) -> bool {
|
||||
if let ValueType::String(_) = self { true } else { false }
|
||||
}
|
||||
|
||||
/// Attempts to get this value as a `String` type. This is different from the `to_string` method.
|
||||
pub fn as_string(&self) -> Option<String> {
|
||||
if let ValueType::String(v) = self {
|
||||
Some(v.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if this is a `Number`.
|
||||
pub fn is_number(&self) -> bool {
|
||||
if let ValueType::Number(_) = self { true } else { false }
|
||||
}
|
||||
|
||||
/// Attempts to get this value as an `i32`.
|
||||
pub fn as_number(&self) -> Option<&i32> {
|
||||
if let ValueType::Number(v) = self {
|
||||
Some(v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if this is a `List`.
|
||||
pub fn is_list(&self) -> bool {
|
||||
if let ValueType::List(_) = self { true } else { false }
|
||||
}
|
||||
|
||||
/// Attempts to get this value as a `Vec<ValueType>`. Each of the values inside the list must
|
||||
/// also be converted later, as these values are any of the possible types in the `ValueType`.
|
||||
pub fn as_list(&self) -> Option<&Vec<ValueType>> {
|
||||
if let ValueType::List(v) = self {
|
||||
Some(v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ValueType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let str = match self {
|
||||
ValueType::String(s) => s.clone(),
|
||||
ValueType::Number(n) => n.to_string(),
|
||||
ValueType::List(l) => format!("{l:?}"),
|
||||
};
|
||||
write!(f, "{}", str)
|
||||
}
|
||||
}
|
||||
+10
-1
@@ -7,12 +7,16 @@ use serde_dynamo::{from_item, to_item};
|
||||
|
||||
use crate::app::Unauthenticated;
|
||||
use crate::common::{StudentInfo, UserClaims};
|
||||
use crate::components::{ActionButton, CheckboxList, Loading, MultiEntry, MultiEntryMember, OutlinedTextField, OutlinedTextFieldPropsBuilder_Error_Repeated_field_disabled, Panel, RadioList, Row, Select};
|
||||
use crate::components::{ActionButton, Loading, OutlinedTextField, Panel, RadioList, Row, Select};
|
||||
use leptos::leptos_dom::logging::console_log;
|
||||
use leptos::prelude::*;
|
||||
use leptos_oidc::{Algorithm, AuthLoaded, AuthSignal, Authenticated};
|
||||
use traits::{AsReactive, ReactiveCapture};
|
||||
|
||||
/// # Get Student Info
|
||||
/// Gets a student's information given their `subject`.
|
||||
///
|
||||
/// All information is found by using a `GetItemCommand` in the student application DynamoDB table.
|
||||
#[server(GetSubmission, endpoint = "/get-submission")]
|
||||
pub async fn get_submission(id: String) -> Result<StudentInfo, ServerFnError> {
|
||||
let config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
|
||||
@@ -47,6 +51,10 @@ pub async fn get_submission(id: String) -> Result<StudentInfo, ServerFnError> {
|
||||
}
|
||||
}
|
||||
|
||||
/// # Create Sample Submission
|
||||
/// Creates a submission given a full `StudentInfo` struct.
|
||||
///
|
||||
/// All information is stored using a `PutItemCommand` in the student application DynamoDB table.
|
||||
#[server(CreateSampleSubmission, endpoint = "/create-sample-submission")]
|
||||
pub async fn create_sample_submission(student_info: StudentInfo) -> Result<(), ServerFnError> {
|
||||
let config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
|
||||
@@ -78,6 +86,7 @@ pub async fn create_sample_submission(student_info: StudentInfo) -> Result<(), S
|
||||
}
|
||||
}
|
||||
|
||||
/// The main home page component. Contains a simple contact form.
|
||||
#[component]
|
||||
pub fn HomePage() -> impl IntoView {
|
||||
// Creates a reactive value to update the button
|
||||
|
||||
@@ -37,6 +37,7 @@ pub fn ProviderEntry() -> impl IntoView {
|
||||
entries=multi_entries
|
||||
name_member=Signal::from(MultiEntryMember::from_str("Entry Name", "name"))
|
||||
info_member=Signal::from(MultiEntryMember::from_str("Entry Info", "info"))
|
||||
schema=vec![MultiEntryMember::from_str("Entry Name", "name"), MultiEntryMember::from_str("Entry Info", "info")]
|
||||
/>
|
||||
</div>
|
||||
</Authenticated>
|
||||
|
||||
Reference in new issue
Block a user