use crate::common::{SchemaContainerStyle, SchemaHeaderStyle, SchemaNode, SchemaType, ValueType}; use leptos::prelude::*; use std::collections::HashMap; #[component] pub fn DataDisplay( /// The schema that this component should use to display the given data. #[prop(into)] schema: Signal, /// The starting key in the data map. #[prop(into, optional)] data_member: Signal, /// A `HashMap` containing all the data that should be displayed. #[prop(into)] data_map: Signal>, ) -> impl IntoView { // The schema should contain information about the header type and styling, along with the // information display. This is rendered as part of the DataDisplayWrapper, which is simply a // wrapper around the information to be displayed. // The children are all SchemaNodes as well, meaning that this component needs to call itself // for each of these children. They will of course contain all the same information that we // expect here. // The actual displayed information is controlled through a DataDisplayView, which will accept // settings for how the information is parsed. For example, rendering a list may be done by // a string join operation, or it may be a list of maps, each of which needs to be displayed with // a specific set of children. The SchemaNode must be able to handle this, and these options // should be given to the DataDisplayView somehow. // As the display_value here is actually an Option, we want to be able to handle cases // where the display is given no values. In this case, the SchemaNode needs to specify whether // a missing value is expected or an error. let display_value = Memo::new(move |_| { let member = data_member.get(); if member.is_empty() { Some(ValueType::Map(Some(data_map.get()))) } else { data_map.get().get(&member).cloned() } }); view! { } } #[component] fn DataDisplayWrapper( #[prop(into)] container_style: SchemaContainerStyle, #[prop(into)] header_style: SchemaHeaderStyle, #[prop(into)] header_text: Signal, children: Children, ) -> impl IntoView { // This component takes a wrapper style and displays the "chrome" of the interface. It has two // options: Text Headers or Capsules. This will then render a DataDisplayHeader, which handles // the styling for the text inside the header specifically. // The capsule style is a colored header and a rounded container. // The text header style is an invisible container. let expanded = RwSignal::new(true); match container_style { SchemaContainerStyle::Header => { // Creates a simple container that only has a header and a series of children. // The spacing between the header and children is controlled by the header style. // This type of container is not collapsable at the moment. view! {
{children()}
} .into_any() } SchemaContainerStyle::Capsule => { // Creates a capsule container - it is collapsable, with rounded corners and a colored // header. view! {
{children()}
} .into_any() } } } #[component] fn DataDisplayHeader( #[prop(into)] header_style: SchemaHeaderStyle, #[prop(into)] header_text: Signal, ) -> impl IntoView { // This component will take a header style and displays the corresponding text. It has a few // options, all of which are defined in the HeaderStyle enum. match header_style { SchemaHeaderStyle::MainHeader => { view! {
{header_text}
}.into_any() } SchemaHeaderStyle::SubsectionHeader => { view! {
{header_text}
}.into_any() } SchemaHeaderStyle::Bold => view! {
{header_text}
}.into_any(), SchemaHeaderStyle::None => view! {}.into_any(), } } #[component] fn DataDisplayView( #[prop(into)] display_data: Signal>, #[prop(into)] schema: Signal, ) -> impl IntoView { // This is the part of the data display that does most of the work. It will take the values // found in the DataDisplay component and perform the logic that actually displays the // information. This component is only displayed if there is Some(value) in the DataDisplay. // In the case that this is displaying a Map or List, it will recursively call the DataDisplay // component. // If this is displaying a PrimitiveList, it will flatten and join all the values into a single // string, then display that string. let data_type = schema.get().data_type; // Matches the specific data type. Displays errors if the given display data does not match. match data_type { SchemaType::String => { let value = Signal::derive(move || { display_data .get() .map(|v| v.as_string().ok().flatten()) .flatten() .unwrap_or("N/A".to_string()) }); view! {
{value}
}.into_any() } SchemaType::Number => { let value = Signal::derive(move || { display_data .get() .map(|v| v.as_number().ok().flatten()) .flatten() .unwrap_or("N/A".to_string()) }); view! {
{value}
}.into_any() } SchemaType::PrimitiveList => { let value = Signal::derive(move || { display_data .get() .map(|v| v.as_list().ok().flatten()) .flatten() .unwrap_or_default() .iter() .map(|v| v.to_string()) .collect::>() .join(", ") }); view! {
{value}
}.into_any() } SchemaType::MapList => { let list = Signal::derive(move || { display_data .get() .map(|v| v.as_list().ok().flatten()) .flatten() .unwrap_or_default() .iter() .filter_map(|v| v.as_map().ok().flatten().clone()) .collect::>>() }); let primary_key = StoredValue::new(schema.get().primary_key.unwrap_or_default()); view! { "List is empty." } > } } /> } .into_any() } SchemaType::Map => { let map = Signal::derive(move || { display_data .get() .map(|v| v.as_map().ok().flatten()) .flatten() }); view! { "Map was not found." } > } } /> } .into_any() } } }