use serde::{Deserialize, Serialize};
use serde_json::Value;
use super::handler::HandlerRef;
use super::signal::SignalId;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum View {
Text(String),
Dynamic(Dynamic),
Element(Element),
Fragment(Fragment),
Component(ComponentMarker),
Island(Island),
Boundary(Boundary),
Slot(SlotView),
Raw(String),
Show(ShowView),
For(ForView),
Match(MatchView),
Empty,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Element {
pub tag: String,
pub attrs: Vec<Attr>,
pub children: Vec<Child>,
pub dom_id: Option<String>,
}
impl Element {
pub(crate) fn merge_attrs(&mut self, extra: impl IntoIterator<Item = Attr>) {
for a in extra {
if a.name == "style" {
if let (Some(existing), AttrValue::Static(extra_style)) =
(self.attrs.iter_mut().find(|x| x.name == "style"), &a.value)
{
if let AttrValue::Static(cur) = &mut existing.value {
append_css(cur, extra_style);
continue;
}
}
}
if let Some(existing) = self.attrs.iter_mut().find(|x| x.name == a.name) {
*existing = a;
} else {
self.attrs.push(a);
}
}
}
}
fn append_css(cur: &mut String, extra: &str) {
let extra = extra.trim();
if extra.is_empty() {
return;
}
if cur.trim().is_empty() {
*cur = extra.to_string();
return;
}
if !cur.trim_end().ends_with(';') {
cur.push(';');
}
if !cur.ends_with(' ') {
cur.push(' ');
}
cur.push_str(extra);
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Fragment {
pub children: Vec<Child>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComponentMarker {
pub name: String,
pub view: Box<View>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SlotView {
pub name: Option<String>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "snake_case")]
pub enum IslandLoad {
#[default]
Eager,
Visible,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Island {
pub chunk_id: String,
pub instance_id: String,
pub signal_ids: Vec<SignalId>,
pub view: Box<View>,
pub props: Value,
#[serde(default)]
pub load: IslandLoad,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Boundary {
pub chunk_id: String,
pub view: Box<View>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ShowView {
pub signal: SignalId,
pub inverted: bool,
pub initial: bool,
pub children: Vec<Child>,
pub fallback: Option<Box<View>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ForView {
pub signal: SignalId,
pub key_field: Option<String>,
pub items: Vec<ForItemView>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub virtual_list: Option<VirtualListOpts>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct VirtualListOpts {
pub item_height: u32,
pub overscan: u32,
pub total: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ForItemView {
pub key: String,
pub children: Vec<Child>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MatchView {
pub signal: SignalId,
pub initial: String,
pub cases: Vec<MatchCase>,
pub default: Option<Vec<Child>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MatchCase {
pub when: String,
pub children: Vec<Child>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Child {
View(View),
Text(String),
}
impl From<View> for Child {
fn from(v: View) -> Self {
Child::View(v)
}
}
impl From<&str> for Child {
fn from(s: &str) -> Self {
Child::Text(s.to_string())
}
}
impl From<String> for Child {
fn from(s: String) -> Self {
Child::Text(s)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Attr {
pub name: String,
pub value: AttrValue,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum AttrValue {
Static(String),
Dynamic {
signal: SignalId,
format: Option<String>,
},
Handler(HandlerRef),
Bool(bool),
PreventDefault(String),
StopPropagation(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Dynamic {
pub signal: SignalId,
pub format: Option<String>,
pub snapshot: Value,
}
impl View {
pub fn text(s: impl Into<String>) -> Self {
View::Text(s.into())
}
pub fn raw(html: impl Into<String>) -> Self {
View::Raw(html.into())
}
pub fn empty() -> Self {
View::Empty
}
pub fn element(tag: impl Into<String>) -> ElementBuilder {
ElementBuilder {
element: Element {
tag: tag.into(),
..Default::default()
},
}
}
pub fn fragment(children: Vec<Child>) -> Self {
View::Fragment(Fragment { children })
}
pub fn slot(name: Option<String>) -> Self {
View::Slot(SlotView { name })
}
pub fn boundary(chunk_id: impl Into<String>, view: View) -> Self {
View::Boundary(Boundary {
chunk_id: chunk_id.into(),
view: Box::new(view),
})
}
}
pub struct ElementBuilder {
element: Element,
}
impl ElementBuilder {
pub fn attr(mut self, name: impl Into<String>, value: AttrValue) -> Self {
self.element.attrs.push(Attr {
name: name.into(),
value,
});
self
}
pub fn child(mut self, child: impl Into<Child>) -> Self {
self.element.children.push(child.into());
self
}
pub fn children(mut self, children: impl IntoIterator<Item = Child>) -> Self {
self.element.children.extend(children);
self
}
pub fn dom_id(mut self, id: impl Into<String>) -> Self {
self.element.dom_id = Some(id.into());
self
}
pub fn build(self) -> View {
View::Element(self.element)
}
}