use crate::reactive::Binding;
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use slotmap::new_key_type;
use std::sync::Arc;
new_key_type! {
pub struct NodeId;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Value {
Static(serde_json::Value),
Binding(Binding),
TemplateString {
template: String,
bindings: Vec<Binding>,
},
Action(String),
Resource(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum IRNode {
Element(Element),
ForEach {
source: Binding,
item_name: String,
key_path: Option<String>,
template: Vec<IRNode>,
props: Props,
module_scope: Option<String>,
},
Conditional {
value: Value,
branches: Vec<ConditionalBranch>,
fallback: Option<Vec<IRNode>>,
module_scope: Option<String>,
},
Router {
location: Value,
routes: Vec<RouterRoute>,
fallback: Option<Vec<IRNode>>,
module_scope: Option<String>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConditionalBranch {
pub pattern: Value,
pub children: Vec<IRNode>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RouterRoute {
pub path: String,
pub children: Vec<IRNode>,
}
impl RouterRoute {
pub fn new(path: impl Into<String>, children: Vec<IRNode>) -> Self {
Self {
path: path.into(),
children,
}
}
}
impl IRNode {
pub fn element(element: Element) -> Self {
IRNode::Element(element)
}
pub fn for_each(
source: Binding,
item_name: impl Into<String>,
key_path: Option<String>,
template: Vec<IRNode>,
props: Props,
) -> Self {
IRNode::ForEach {
source,
item_name: item_name.into(),
key_path,
template,
props,
module_scope: None,
}
}
pub fn conditional(
value: Value,
branches: Vec<ConditionalBranch>,
fallback: Option<Vec<IRNode>>,
) -> Self {
IRNode::Conditional {
value,
branches,
fallback,
module_scope: None,
}
}
pub fn router(
location: Value,
routes: Vec<RouterRoute>,
fallback: Option<Vec<IRNode>>,
) -> Self {
IRNode::Router {
location,
routes,
fallback,
module_scope: None,
}
}
pub fn as_element(&self) -> Option<&Element> {
match self {
IRNode::Element(e) => Some(e),
_ => None,
}
}
pub fn is_for_each(&self) -> bool {
matches!(self, IRNode::ForEach { .. })
}
pub fn is_conditional(&self) -> bool {
matches!(self, IRNode::Conditional { .. })
}
pub fn is_router(&self) -> bool {
matches!(self, IRNode::Router { .. })
}
}
impl ConditionalBranch {
pub fn new(pattern: Value, children: Vec<IRNode>) -> Self {
Self { pattern, children }
}
}
pub type PropsMap = IndexMap<String, Value>;
#[derive(Debug, Clone)]
pub struct Props(Arc<PropsMap>);
impl Props {
pub fn new() -> Self {
Props(Arc::new(IndexMap::new()))
}
pub fn from_map(map: PropsMap) -> Self {
Props(Arc::new(map))
}
pub fn make_mut(&mut self) -> &mut PropsMap {
Arc::make_mut(&mut self.0)
}
pub fn insert(&mut self, key: String, value: Value) -> Option<Value> {
self.make_mut().insert(key, value)
}
pub fn remove(&mut self, key: &str) -> Option<Value> {
self.make_mut().shift_remove(key)
}
pub fn inner(&self) -> &PropsMap {
&self.0
}
}
impl Default for Props {
fn default() -> Self {
Props::new()
}
}
impl std::ops::Deref for Props {
type Target = PropsMap;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'a> IntoIterator for &'a Props {
type Item = (&'a String, &'a Value);
type IntoIter = indexmap::map::Iter<'a, String, Value>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
impl FromIterator<(String, Value)> for Props {
fn from_iter<I: IntoIterator<Item = (String, Value)>>(iter: I) -> Self {
Props(Arc::new(iter.into_iter().collect()))
}
}
impl serde::Serialize for Props {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.0.serialize(serializer)
}
}
impl<'de> serde::Deserialize<'de> for Props {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let map = PropsMap::deserialize(deserializer)?;
Ok(Props(Arc::new(map)))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Element {
pub element_type: String,
pub props: Props,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub ir_children: Vec<IRNode>,
pub key: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub module_scope: Option<String>,
}
impl Element {
pub fn new(element_type: impl Into<String>) -> Self {
Self {
element_type: element_type.into(),
props: Props::new(),
ir_children: Vec::new(),
key: None,
module_scope: None,
}
}
pub fn with_prop(mut self, key: impl Into<String>, value: Value) -> Self {
self.props.insert(key.into(), value);
self
}
pub fn with_child(mut self, child: Element) -> Self {
self.ir_children.push(IRNode::Element(child));
self
}
pub fn with_key(mut self, key: impl Into<String>) -> Self {
self.key = Some(key.into());
self
}
}