#![cfg_attr(feature = "fail-on-warnings", deny(warnings))]
#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
#![allow(clippy::multiple_crate_versions)]
use std::collections::BTreeMap;
use hyperchad_transformer_models::Visibility;
#[cfg(feature = "logic")]
pub mod logic;
#[cfg(feature = "arb")]
pub mod arb;
#[cfg(feature = "handler")]
pub mod handler;
pub mod dsl;
pub use hyperchad_transformer_models::{ElementTarget, Target};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Key {
Escape,
Tab,
ArrowUp,
ArrowDown,
ArrowLeft,
ArrowRight,
Home,
End,
PageUp,
PageDown,
Delete,
Backspace,
Enter,
Insert,
F1,
F2,
F3,
F4,
F5,
F6,
F7,
F8,
F9,
F10,
F11,
F12,
NumLock,
ScrollLock,
CapsLock,
Shift,
Control,
Alt,
Meta,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z,
Key0,
Key1,
Key2,
Key3,
Key4,
Key5,
Key6,
Key7,
Key8,
Key9,
Numpad0,
Numpad1,
Numpad2,
Numpad3,
Numpad4,
Numpad5,
Numpad6,
Numpad7,
Numpad8,
Numpad9,
}
impl Key {
#[must_use]
pub const fn as_str(&self) -> &'static str {
match self {
Self::Escape => "Escape",
Self::Tab => "Tab",
Self::ArrowUp => "ArrowUp",
Self::ArrowDown => "ArrowDown",
Self::ArrowLeft => "ArrowLeft",
Self::ArrowRight => "ArrowRight",
Self::Home => "Home",
Self::End => "End",
Self::PageUp => "PageUp",
Self::PageDown => "PageDown",
Self::Delete => "Delete",
Self::Backspace => "Backspace",
Self::Enter => "Enter",
Self::Insert => "Insert",
Self::F1 => "F1",
Self::F2 => "F2",
Self::F3 => "F3",
Self::F4 => "F4",
Self::F5 => "F5",
Self::F6 => "F6",
Self::F7 => "F7",
Self::F8 => "F8",
Self::F9 => "F9",
Self::F10 => "F10",
Self::F11 => "F11",
Self::F12 => "F12",
Self::NumLock => "NumLock",
Self::ScrollLock => "ScrollLock",
Self::CapsLock => "CapsLock",
Self::Shift => "Shift",
Self::Control => "Control",
Self::Alt => "Alt",
Self::Meta => "Meta",
Self::A => "A",
Self::B => "B",
Self::C => "C",
Self::D => "D",
Self::E => "E",
Self::F => "F",
Self::G => "G",
Self::H => "H",
Self::I => "I",
Self::J => "J",
Self::K => "K",
Self::L => "L",
Self::M => "M",
Self::N => "N",
Self::O => "O",
Self::P => "P",
Self::Q => "Q",
Self::R => "R",
Self::S => "S",
Self::T => "T",
Self::U => "U",
Self::V => "V",
Self::W => "W",
Self::X => "X",
Self::Y => "Y",
Self::Z => "Z",
Self::Key0 => "Key0",
Self::Key1 => "Key1",
Self::Key2 => "Key2",
Self::Key3 => "Key3",
Self::Key4 => "Key4",
Self::Key5 => "Key5",
Self::Key6 => "Key6",
Self::Key7 => "Key7",
Self::Key8 => "Key8",
Self::Key9 => "Key9",
Self::Numpad0 => "Numpad0",
Self::Numpad1 => "Numpad1",
Self::Numpad2 => "Numpad2",
Self::Numpad3 => "Numpad3",
Self::Numpad4 => "Numpad4",
Self::Numpad5 => "Numpad5",
Self::Numpad6 => "Numpad6",
Self::Numpad7 => "Numpad7",
Self::Numpad8 => "Numpad8",
Self::Numpad9 => "Numpad9",
}
}
}
impl std::fmt::Display for Key {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct HttpEventContext {
pub url: String,
pub method: String,
pub status: Option<u16>,
pub headers: Option<BTreeMap<String, String>>,
pub duration_ms: Option<u64>,
pub error: Option<String>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ActionTrigger {
Click,
ClickOutside,
MouseDown,
KeyDown,
Hover,
Change,
Resize,
Event(String),
#[default]
Immediate,
HttpBeforeRequest,
HttpAfterRequest,
HttpRequestSuccess,
HttpRequestError,
HttpRequestAbort,
HttpRequestTimeout,
}
impl ActionTrigger {
#[must_use]
pub const fn trigger_type(&self) -> &'static str {
match self {
Self::Click => "Click",
Self::ClickOutside => "ClickOutside",
Self::MouseDown => "MouseDown",
Self::KeyDown => "KeyDown",
Self::Hover => "Hover",
Self::Change => "Change",
Self::Resize => "Resize",
Self::Event(_) => "Event",
Self::Immediate => "Immediate",
Self::HttpBeforeRequest => "HttpBeforeRequest",
Self::HttpAfterRequest => "HttpAfterRequest",
Self::HttpRequestSuccess => "HttpRequestSuccess",
Self::HttpRequestError => "HttpRequestError",
Self::HttpRequestAbort => "HttpRequestAbort",
Self::HttpRequestTimeout => "HttpRequestTimeout",
}
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type"))]
pub struct Action {
pub trigger: ActionTrigger,
pub effect: ActionEffect,
}
#[cfg(feature = "serde")]
impl std::fmt::Display for Action {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&serde_json::to_string(self).unwrap())
}
}
#[derive(Clone, Debug, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type"))]
pub struct ActionEffect {
pub action: ActionType,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub delay_off: Option<u64>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub throttle: Option<u64>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub unique: Option<bool>,
}
impl ActionEffect {
#[must_use]
pub const fn into_action_effect(self) -> Self {
self
}
}
impl From<Vec<Self>> for ActionEffect {
fn from(value: Vec<Self>) -> Self {
Self {
action: ActionType::MultiEffect(value),
..Default::default()
}
}
}
impl From<Vec<ActionType>> for ActionEffect {
fn from(value: Vec<ActionType>) -> Self {
Self {
action: ActionType::Multi(value),
..Default::default()
}
}
}
impl ActionEffect {
#[must_use]
pub const fn delay_off(mut self, millis: u64) -> Self {
self.delay_off = Some(millis);
self
}
#[must_use]
pub const fn throttle(mut self, millis: u64) -> Self {
self.throttle = Some(millis);
self
}
#[must_use]
pub const fn unique(mut self) -> Self {
self.unique = Some(true);
self
}
}
impl From<ActionType> for ActionEffect {
fn from(value: ActionType) -> Self {
Self {
action: value,
..Default::default()
}
}
}
impl From<Box<ActionType>> for ActionEffect {
fn from(value: Box<ActionType>) -> Self {
(*value).into()
}
}
#[cfg(feature = "serde")]
impl std::fmt::Display for ActionEffect {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&serde_json::to_string(self).unwrap())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum LogLevel {
Error,
Warn,
Info,
Debug,
Trace,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum InputActionType {
Select {
target: ElementTarget,
},
}
#[derive(Clone, Debug, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ActionType {
#[default]
NoOp,
Let {
name: String,
value: dsl::Expression,
},
Style {
target: ElementTarget,
action: StyleAction,
},
Input(InputActionType),
Navigate {
url: String,
},
Log {
message: String,
level: LogLevel,
},
Custom {
action: String,
},
Event {
name: String,
action: Box<Self>,
},
Multi(Vec<Self>),
MultiEffect(Vec<ActionEffect>),
#[cfg(feature = "logic")]
Parameterized {
action: Box<Self>,
value: logic::Value,
},
#[cfg(feature = "logic")]
Logic(logic::If),
}
impl ActionType {
#[must_use]
pub fn into_action_effect(self) -> ActionEffect {
self.into()
}
}
impl ActionType {
#[must_use]
pub fn on_event(event: impl Into<String>, action: impl Into<Self>) -> Self {
Self::Event {
name: event.into(),
action: Box::new(action.into()),
}
}
#[must_use]
pub fn set_focus_by_id(focus: bool, target: impl Into<Target>) -> Self {
Self::Style {
target: ElementTarget::ById(target.into()),
action: StyleAction::SetFocus(focus),
}
}
#[must_use]
pub fn set_focus_class(focus: bool, class: impl Into<Target>) -> Self {
Self::Style {
target: ElementTarget::Class(class.into()),
action: StyleAction::SetFocus(focus),
}
}
#[must_use]
pub fn set_focus_child_class(focus: bool, class: impl Into<Target>) -> Self {
Self::Style {
target: ElementTarget::ChildClass(class.into()),
action: StyleAction::SetFocus(focus),
}
}
#[must_use]
pub fn select_by_id(target: impl Into<Target>) -> Self {
Self::Input(InputActionType::Select {
target: ElementTarget::ById(target.into()),
})
}
#[must_use]
pub fn select_class(class: impl Into<Target>) -> Self {
Self::Input(InputActionType::Select {
target: ElementTarget::Class(class.into()),
})
}
#[must_use]
pub fn select_child_class(class: impl Into<Target>) -> Self {
Self::Input(InputActionType::Select {
target: ElementTarget::ChildClass(class.into()),
})
}
#[must_use]
pub fn set_visibility_by_id(visibility: Visibility, target: impl Into<Target>) -> Self {
Self::Style {
target: ElementTarget::ById(target.into()),
action: StyleAction::SetVisibility(visibility),
}
}
#[must_use]
pub fn set_visibility_child_class(visibility: Visibility, class: impl Into<Target>) -> Self {
Self::Style {
target: ElementTarget::ChildClass(class.into()),
action: StyleAction::SetVisibility(visibility),
}
}
#[must_use]
pub fn hide_by_id(target: impl Into<Target>) -> Self {
Self::set_visibility_by_id(Visibility::Hidden, target)
}
#[must_use]
pub fn show_by_id(target: impl Into<Target>) -> Self {
Self::set_visibility_by_id(Visibility::Visible, target)
}
#[must_use]
pub fn hide_class(class_name: impl Into<Target>) -> Self {
Self::set_visibility_class(Visibility::Hidden, class_name)
}
#[must_use]
pub fn show_class(class_name: impl Into<Target>) -> Self {
Self::set_visibility_class(Visibility::Visible, class_name)
}
#[must_use]
pub fn focus_by_id(target: impl Into<Target>) -> Self {
Self::set_focus_by_id(true, target)
}
#[must_use]
pub fn focus_class(class_name: impl Into<Target>) -> Self {
Self::set_focus_class(true, class_name)
}
#[must_use]
pub fn set_visibility_class(visibility: Visibility, class_name: impl Into<Target>) -> Self {
Self::Style {
target: ElementTarget::Class(class_name.into()),
action: StyleAction::SetVisibility(visibility),
}
}
#[must_use]
pub const fn set_visibility_id(visibility: Visibility, target: usize) -> Self {
Self::Style {
target: ElementTarget::Id(target),
action: StyleAction::SetVisibility(visibility),
}
}
#[must_use]
pub const fn hide_id(target: usize) -> Self {
Self::set_visibility_id(Visibility::Hidden, target)
}
#[must_use]
pub const fn show_id(target: usize) -> Self {
Self::set_visibility_id(Visibility::Visible, target)
}
#[must_use]
pub const fn set_visibility_self(visibility: Visibility) -> Self {
Self::Style {
target: ElementTarget::SelfTarget,
action: StyleAction::SetVisibility(visibility),
}
}
#[must_use]
pub const fn hide_self() -> Self {
Self::set_visibility_self(Visibility::Hidden)
}
#[must_use]
pub const fn show_self() -> Self {
Self::set_visibility_self(Visibility::Visible)
}
#[must_use]
pub const fn set_visibility_last_child(visibility: Visibility) -> Self {
Self::Style {
target: ElementTarget::LastChild,
action: StyleAction::SetVisibility(visibility),
}
}
#[must_use]
pub const fn hide_last_child() -> Self {
Self::set_visibility_last_child(Visibility::Hidden)
}
#[must_use]
pub const fn show_last_child() -> Self {
Self::set_visibility_last_child(Visibility::Visible)
}
#[cfg(feature = "logic")]
#[must_use]
pub fn toggle_visibility_by_id(target: impl Into<Target>) -> Self {
let target = target.into();
Self::Logic(crate::logic::If {
condition: crate::logic::Condition::Eq(
crate::logic::get_visibility_by_id(&target).into(),
crate::logic::Value::Visibility(hyperchad_transformer_models::Visibility::Visible),
),
actions: vec![crate::ActionEffect {
action: Self::Style {
target: crate::ElementTarget::ById(target.clone()),
action: crate::StyleAction::SetVisibility(
hyperchad_transformer_models::Visibility::Hidden,
),
},
..Default::default()
}],
else_actions: vec![crate::ActionEffect {
action: Self::Style {
target: crate::ElementTarget::ById(target),
action: crate::StyleAction::SetVisibility(
hyperchad_transformer_models::Visibility::Visible,
),
},
..Default::default()
}],
})
}
#[cfg(feature = "logic")]
#[must_use]
pub fn toggle_visibility_selector(target: impl Into<Target>) -> Self {
let target = target.into();
Self::Logic(crate::logic::If {
condition: crate::logic::Condition::Eq(
crate::logic::get_visibility_selector(&target).into(),
crate::logic::Value::Visibility(hyperchad_transformer_models::Visibility::Visible),
),
actions: vec![crate::ActionEffect {
action: Self::Style {
target: crate::ElementTarget::Selector(target.clone()),
action: crate::StyleAction::SetVisibility(
hyperchad_transformer_models::Visibility::Hidden,
),
},
..Default::default()
}],
else_actions: vec![crate::ActionEffect {
action: Self::Style {
target: crate::ElementTarget::Selector(target),
action: crate::StyleAction::SetVisibility(
hyperchad_transformer_models::Visibility::Visible,
),
},
..Default::default()
}],
})
}
#[must_use]
pub fn set_display_by_id(display: bool, target: impl Into<Target>) -> Self {
Self::Style {
target: ElementTarget::ById(target.into()),
action: StyleAction::SetDisplay(display),
}
}
#[must_use]
pub fn set_display_class(display: bool, class_name: impl Into<Target>) -> Self {
Self::Style {
target: ElementTarget::Class(class_name.into()),
action: StyleAction::SetDisplay(display),
}
}
#[must_use]
pub fn no_display_by_id(target: impl Into<Target>) -> Self {
Self::set_display_by_id(false, target)
}
#[must_use]
pub fn display_by_id(target: impl Into<Target>) -> Self {
Self::set_display_by_id(true, target)
}
#[must_use]
pub fn no_display_class(class_name: impl Into<Target>) -> Self {
Self::set_display_class(false, class_name)
}
#[must_use]
pub fn display_class(class_name: impl Into<Target>) -> Self {
Self::set_display_class(true, class_name)
}
#[must_use]
pub const fn set_display_id(display: bool, target: usize) -> Self {
Self::Style {
target: ElementTarget::Id(target),
action: StyleAction::SetDisplay(display),
}
}
#[must_use]
pub const fn no_display_id(target: usize) -> Self {
Self::set_display_id(false, target)
}
#[must_use]
pub const fn display_id(target: usize) -> Self {
Self::set_display_id(true, target)
}
#[must_use]
pub const fn set_display_self(display: bool) -> Self {
Self::Style {
target: ElementTarget::SelfTarget,
action: StyleAction::SetDisplay(display),
}
}
#[must_use]
pub const fn no_display_self() -> Self {
Self::set_display_self(false)
}
#[must_use]
pub const fn display_self() -> Self {
Self::set_display_self(true)
}
#[must_use]
pub const fn set_display_last_child(display: bool) -> Self {
Self::Style {
target: ElementTarget::LastChild,
action: StyleAction::SetDisplay(display),
}
}
#[must_use]
pub const fn no_display_last_child() -> Self {
Self::set_display_last_child(false)
}
#[must_use]
pub const fn display_last_child() -> Self {
Self::set_display_last_child(true)
}
#[must_use]
pub fn set_display_child_class(display: bool, class: impl Into<Target>) -> Self {
Self::Style {
target: ElementTarget::ChildClass(class.into()),
action: StyleAction::SetDisplay(display),
}
}
#[must_use]
pub fn no_display_child_class(class: impl Into<Target>) -> Self {
Self::set_display_child_class(false, class)
}
#[must_use]
pub fn display_child_class(class: impl Into<Target>) -> Self {
Self::set_display_child_class(true, class)
}
#[cfg(feature = "logic")]
#[must_use]
pub fn toggle_display_by_id(target: impl Into<Target>) -> Self {
let target = target.into();
Self::Logic(crate::logic::If {
condition: crate::logic::Condition::Eq(
crate::logic::get_display_by_id(&target).into(),
crate::logic::Value::Display(true),
),
actions: vec![crate::ActionEffect {
action: Self::Style {
target: crate::ElementTarget::by_id(&target),
action: crate::StyleAction::SetDisplay(false),
},
..Default::default()
}],
else_actions: vec![crate::ActionEffect {
action: Self::Style {
target: crate::ElementTarget::by_id(&target),
action: crate::StyleAction::SetDisplay(true),
},
..Default::default()
}],
})
}
#[cfg(feature = "logic")]
#[must_use]
pub fn toggle_display_str_class(target: impl Into<Target>) -> Self {
let target = target.into();
Self::Logic(crate::logic::If {
condition: crate::logic::Condition::Eq(
crate::logic::get_display_class(&target).into(),
crate::logic::Value::Display(true),
),
actions: vec![crate::ActionEffect {
action: Self::Style {
target: crate::ElementTarget::class(&target),
action: crate::StyleAction::SetDisplay(false),
},
..Default::default()
}],
else_actions: vec![crate::ActionEffect {
action: Self::Style {
target: crate::ElementTarget::class(&target),
action: crate::StyleAction::SetDisplay(true),
},
..Default::default()
}],
})
}
#[cfg(feature = "logic")]
#[must_use]
pub fn toggle_display_child_class(target: impl Into<Target>) -> Self {
let target = target.into();
Self::Logic(crate::logic::If {
condition: crate::logic::Condition::Eq(
crate::logic::get_display_child_class(&target).into(),
crate::logic::Value::Display(true),
),
actions: vec![crate::ActionEffect {
action: Self::Style {
target: crate::ElementTarget::child_class(&target),
action: crate::StyleAction::SetDisplay(false),
},
..Default::default()
}],
else_actions: vec![crate::ActionEffect {
action: Self::Style {
target: crate::ElementTarget::child_class(&target),
action: crate::StyleAction::SetDisplay(true),
},
..Default::default()
}],
})
}
#[cfg(feature = "logic")]
#[must_use]
pub fn toggle_display_id(target: usize) -> Self {
Self::Logic(crate::logic::If {
condition: crate::logic::Condition::Eq(
crate::logic::Value::Calc(crate::logic::get_display_id(target)),
crate::logic::Value::Display(true),
),
actions: vec![crate::ActionEffect {
action: Self::Style {
target: crate::ElementTarget::Id(target),
action: crate::StyleAction::SetDisplay(false),
},
delay_off: None,
throttle: None,
unique: None,
}],
else_actions: vec![crate::ActionEffect {
action: Self::Style {
target: crate::ElementTarget::Id(target),
action: crate::StyleAction::SetDisplay(true),
},
delay_off: None,
throttle: None,
unique: None,
}],
})
}
#[cfg(feature = "logic")]
#[must_use]
pub fn toggle_display_self() -> Self {
Self::Logic(crate::logic::If {
condition: crate::logic::Condition::Eq(
crate::logic::Value::Calc(crate::logic::get_display_self()),
crate::logic::Value::Display(true),
),
actions: vec![crate::ActionEffect {
action: Self::Style {
target: crate::ElementTarget::SelfTarget,
action: crate::StyleAction::SetDisplay(false),
},
delay_off: None,
throttle: None,
unique: None,
}],
else_actions: vec![crate::ActionEffect {
action: Self::Style {
target: crate::ElementTarget::SelfTarget,
action: crate::StyleAction::SetDisplay(true),
},
delay_off: None,
throttle: None,
unique: None,
}],
})
}
#[cfg(feature = "logic")]
#[must_use]
pub fn toggle_display_last_child() -> Self {
Self::Logic(crate::logic::If {
condition: crate::logic::Condition::Eq(
crate::logic::Value::Calc(crate::logic::get_display_last_child()),
crate::logic::Value::Display(true),
),
actions: vec![crate::ActionEffect {
action: Self::Style {
target: crate::ElementTarget::LastChild,
action: crate::StyleAction::SetDisplay(false),
},
delay_off: None,
throttle: None,
unique: None,
}],
else_actions: vec![crate::ActionEffect {
action: Self::Style {
target: crate::ElementTarget::LastChild,
action: crate::StyleAction::SetDisplay(true),
},
delay_off: None,
throttle: None,
unique: None,
}],
})
}
#[must_use]
pub fn set_background_by_id(background: impl Into<String>, target: impl Into<Target>) -> Self {
Self::Style {
target: ElementTarget::ById(target.into()),
action: StyleAction::SetBackground(Some(background.into())),
}
}
#[must_use]
pub fn set_background_id(background: impl Into<String>, target: usize) -> Self {
Self::Style {
target: ElementTarget::Id(target),
action: StyleAction::SetBackground(Some(background.into())),
}
}
#[must_use]
pub fn set_background_self(background: impl Into<String>) -> Self {
Self::Style {
target: ElementTarget::SelfTarget,
action: StyleAction::SetBackground(Some(background.into())),
}
}
#[must_use]
pub const fn remove_background_self() -> Self {
Self::Style {
target: ElementTarget::SelfTarget,
action: StyleAction::SetBackground(None),
}
}
#[must_use]
pub fn remove_background_by_id(target: impl Into<Target>) -> Self {
Self::Style {
target: ElementTarget::ById(target.into()),
action: StyleAction::SetBackground(None),
}
}
#[must_use]
pub const fn remove_background_id(target: usize) -> Self {
Self::Style {
target: ElementTarget::Id(target),
action: StyleAction::SetBackground(None),
}
}
#[must_use]
pub fn remove_background_class(class_name: impl Into<Target>) -> Self {
Self::Style {
target: ElementTarget::Class(class_name.into()),
action: StyleAction::SetBackground(None),
}
}
#[must_use]
pub fn remove_background_child_class(class_name: impl Into<Target>) -> Self {
Self::Style {
target: ElementTarget::ChildClass(class_name.into()),
action: StyleAction::SetBackground(None),
}
}
#[must_use]
pub const fn remove_background_last_child() -> Self {
Self::Style {
target: ElementTarget::LastChild,
action: StyleAction::SetBackground(None),
}
}
#[must_use]
pub fn set_background_last_child(background: impl Into<String>) -> Self {
Self::Style {
target: ElementTarget::LastChild,
action: StyleAction::SetBackground(Some(background.into())),
}
}
#[must_use]
pub fn and(self, action: impl Into<Self>) -> Self {
if let Self::Multi(mut actions) = self {
actions.push(action.into());
Self::Multi(actions)
} else {
Self::Multi(vec![self, action.into()])
}
}
#[must_use]
pub const fn throttle(self, millis: u64) -> ActionEffect {
ActionEffect {
action: self,
throttle: Some(millis),
delay_off: None,
unique: None,
}
}
#[must_use]
pub const fn delay_off(self, millis: u64) -> ActionEffect {
ActionEffect {
action: self,
throttle: None,
delay_off: Some(millis),
unique: None,
}
}
#[must_use]
pub const fn unique(self) -> ActionEffect {
ActionEffect {
action: self,
throttle: None,
delay_off: None,
unique: Some(true),
}
}
}
#[cfg(feature = "logic")]
impl From<logic::If> for ActionType {
fn from(value: logic::If) -> Self {
Self::Logic(value)
}
}
impl From<ActionType> for Action {
fn from(value: ActionType) -> Self {
Self {
trigger: ActionTrigger::default(),
effect: ActionEffect {
action: value,
delay_off: None,
throttle: None,
unique: None,
},
}
}
}
#[cfg(feature = "serde")]
impl std::fmt::Display for ActionType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&serde_json::to_string(self).unwrap())
}
}
#[cfg(feature = "serde")]
impl<'a> TryFrom<&'a str> for ActionType {
type Error = serde_json::Error;
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
serde_json::from_str(value)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum StyleAction {
SetVisibility(Visibility),
SetFocus(bool),
SetDisplay(bool),
SetBackground(Option<String>),
}
#[cfg(feature = "serde")]
impl std::fmt::Display for StyleAction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&serde_json::to_string(self).unwrap())
}
}
#[cfg(feature = "serde")]
impl<'a> TryFrom<&'a str> for StyleAction {
type Error = serde_json::Error;
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
serde_json::from_str(value)
}
}
pub mod prelude {
pub use crate::{
Action, ActionEffect, ActionTrigger, ActionType, ElementTarget, LogLevel, StyleAction,
dsl::*,
};
#[cfg(feature = "logic")]
pub use crate::logic::*;
}
#[cfg(test)]
mod tests {
use super::*;
#[test_log::test]
fn test_action_effect_delay_off() {
let action = ActionType::NoOp;
let effect = action.delay_off(1000);
assert_eq!(effect.delay_off, Some(1000));
assert_eq!(effect.throttle, None);
assert_eq!(effect.unique, None);
}
#[test_log::test]
fn test_action_effect_throttle() {
let action = ActionType::NoOp;
let effect = action.throttle(500);
assert_eq!(effect.throttle, Some(500));
assert_eq!(effect.delay_off, None);
assert_eq!(effect.unique, None);
}
#[test_log::test]
fn test_action_effect_unique() {
let action = ActionType::NoOp;
let effect = action.unique();
assert_eq!(effect.unique, Some(true));
assert_eq!(effect.throttle, None);
assert_eq!(effect.delay_off, None);
}
#[test_log::test]
fn test_action_effect_chaining() {
let effect = ActionEffect::default()
.delay_off(1000)
.throttle(500)
.unique();
assert_eq!(effect.delay_off, Some(1000));
assert_eq!(effect.throttle, Some(500));
assert_eq!(effect.unique, Some(true));
}
#[test_log::test]
fn test_action_type_hide_by_id() {
let action = ActionType::hide_by_id("my-element");
match action {
ActionType::Style { target, action } => {
assert_eq!(target, ElementTarget::ById(Target::from("my-element")));
assert_eq!(action, StyleAction::SetVisibility(Visibility::Hidden));
}
_ => panic!("Expected Style action"),
}
}
#[test_log::test]
fn test_action_type_show_by_id() {
let action = ActionType::show_by_id("my-element");
match action {
ActionType::Style { target, action } => {
assert_eq!(target, ElementTarget::ById(Target::from("my-element")));
assert_eq!(action, StyleAction::SetVisibility(Visibility::Visible));
}
_ => panic!("Expected Style action"),
}
}
#[test_log::test]
fn test_action_type_hide_id() {
let action = ActionType::hide_id(42);
match action {
ActionType::Style { target, action } => {
assert_eq!(target, ElementTarget::Id(42));
assert_eq!(action, StyleAction::SetVisibility(Visibility::Hidden));
}
_ => panic!("Expected Style action"),
}
}
#[test_log::test]
fn test_action_type_show_id() {
let action = ActionType::show_id(42);
match action {
ActionType::Style { target, action } => {
assert_eq!(target, ElementTarget::Id(42));
assert_eq!(action, StyleAction::SetVisibility(Visibility::Visible));
}
_ => panic!("Expected Style action"),
}
}
#[test_log::test]
fn test_action_type_hide_self() {
let action = ActionType::hide_self();
match action {
ActionType::Style { target, action } => {
assert_eq!(target, ElementTarget::SelfTarget);
assert_eq!(action, StyleAction::SetVisibility(Visibility::Hidden));
}
_ => panic!("Expected Style action"),
}
}
#[test_log::test]
fn test_action_type_show_self() {
let action = ActionType::show_self();
match action {
ActionType::Style { target, action } => {
assert_eq!(target, ElementTarget::SelfTarget);
assert_eq!(action, StyleAction::SetVisibility(Visibility::Visible));
}
_ => panic!("Expected Style action"),
}
}
#[test_log::test]
fn test_action_type_display_by_id() {
let action = ActionType::display_by_id("my-element");
match action {
ActionType::Style { target, action } => {
assert_eq!(target, ElementTarget::ById(Target::from("my-element")));
assert_eq!(action, StyleAction::SetDisplay(true));
}
_ => panic!("Expected Style action"),
}
}
#[test_log::test]
fn test_action_type_no_display_by_id() {
let action = ActionType::no_display_by_id("my-element");
match action {
ActionType::Style { target, action } => {
assert_eq!(target, ElementTarget::ById(Target::from("my-element")));
assert_eq!(action, StyleAction::SetDisplay(false));
}
_ => panic!("Expected Style action"),
}
}
#[test_log::test]
fn test_action_type_set_focus() {
let action = ActionType::set_focus_by_id(true, "my-element");
match action {
ActionType::Style { target, action } => {
assert_eq!(target, ElementTarget::ById(Target::from("my-element")));
assert_eq!(action, StyleAction::SetFocus(true));
}
_ => panic!("Expected Style action"),
}
}
#[test_log::test]
fn test_action_type_focus_by_id() {
let action = ActionType::focus_by_id("my-element");
match action {
ActionType::Style { target, action } => {
assert_eq!(target, ElementTarget::ById(Target::from("my-element")));
assert_eq!(action, StyleAction::SetFocus(true));
}
_ => panic!("Expected Style action"),
}
}
#[test_log::test]
fn test_action_type_set_background() {
let action = ActionType::set_background_by_id("red", "my-element");
match action {
ActionType::Style { target, action } => {
assert_eq!(target, ElementTarget::ById(Target::from("my-element")));
assert_eq!(action, StyleAction::SetBackground(Some("red".to_string())));
}
_ => panic!("Expected Style action"),
}
}
#[test_log::test]
fn test_action_type_remove_background() {
let action = ActionType::remove_background_by_id("my-element");
match action {
ActionType::Style { target, action } => {
assert_eq!(target, ElementTarget::ById(Target::from("my-element")));
assert_eq!(action, StyleAction::SetBackground(None));
}
_ => panic!("Expected Style action"),
}
}
#[test_log::test]
fn test_action_type_navigate() {
let action = ActionType::Navigate {
url: "/home".to_string(),
};
match action {
ActionType::Navigate { url } => {
assert_eq!(url, "/home");
}
_ => panic!("Expected Navigate action"),
}
}
#[test_log::test]
fn test_action_type_log() {
let action = ActionType::Log {
message: "test".to_string(),
level: LogLevel::Info,
};
match action {
ActionType::Log { message, level } => {
assert_eq!(message, "test");
assert_eq!(level, LogLevel::Info);
}
_ => panic!("Expected Log action"),
}
}
#[test_log::test]
#[allow(clippy::similar_names)]
fn test_action_type_and_chaining() {
let action1 = ActionType::hide_by_id("element1");
let action2 = ActionType::show_by_id("element2");
let chained = action1.and(action2);
match chained {
ActionType::Multi(actions) => {
assert_eq!(actions.len(), 2);
}
_ => panic!("Expected Multi action"),
}
}
#[test_log::test]
#[allow(clippy::similar_names)]
fn test_action_type_and_chaining_with_existing_multi() {
let action1 = ActionType::hide_by_id("element1");
let action2 = ActionType::show_by_id("element2");
let action3 = ActionType::no_display_by_id("element3");
let chained = action1.and(action2).and(action3);
match chained {
ActionType::Multi(actions) => {
assert_eq!(actions.len(), 3);
}
_ => panic!("Expected Multi action"),
}
}
#[test_log::test]
fn test_action_type_on_event() {
let inner_action = ActionType::show_by_id("element");
let event_action = ActionType::on_event("custom-event", inner_action);
match event_action {
ActionType::Event { name, action } => {
assert_eq!(name, "custom-event");
match *action {
ActionType::Style { .. } => {}
_ => panic!("Expected Style action inside Event"),
}
}
_ => panic!("Expected Event action"),
}
}
#[test_log::test]
fn test_action_type_select_by_id() {
let action = ActionType::select_by_id("input-field");
match action {
ActionType::Input(InputActionType::Select { target }) => {
assert_eq!(target, ElementTarget::ById(Target::from("input-field")));
}
_ => panic!("Expected Input Select action"),
}
}
#[test_log::test]
fn test_action_effect_from_vec_action_types() {
let actions = vec![
ActionType::hide_by_id("element1"),
ActionType::show_by_id("element2"),
];
let effect: ActionEffect = actions.into();
match effect.action {
ActionType::Multi(inner_actions) => {
assert_eq!(inner_actions.len(), 2);
}
_ => panic!("Expected Multi action"),
}
}
#[test_log::test]
fn test_action_effect_from_vec_action_effects() {
let effects = vec![
ActionEffect {
action: ActionType::hide_by_id("element1"),
delay_off: Some(100),
throttle: None,
unique: None,
},
ActionEffect {
action: ActionType::show_by_id("element2"),
delay_off: None,
throttle: Some(200),
unique: None,
},
];
let combined: ActionEffect = effects.into();
match combined.action {
ActionType::MultiEffect(inner_effects) => {
assert_eq!(inner_effects.len(), 2);
assert_eq!(inner_effects[0].delay_off, Some(100));
assert_eq!(inner_effects[1].throttle, Some(200));
}
_ => panic!("Expected MultiEffect action"),
}
}
#[test_log::test]
fn test_action_from_action_type() {
let action_type = ActionType::NoOp;
let action: Action = action_type.into();
assert_eq!(action.trigger, ActionTrigger::Immediate);
assert_eq!(action.effect.action, ActionType::NoOp);
}
#[test_log::test]
fn test_http_event_context_creation() {
let context = HttpEventContext {
url: "https://example.com".to_string(),
method: "GET".to_string(),
status: Some(200),
headers: Some(BTreeMap::new()),
duration_ms: Some(150),
error: None,
};
assert_eq!(context.url, "https://example.com");
assert_eq!(context.method, "GET");
assert_eq!(context.status, Some(200));
assert_eq!(context.duration_ms, Some(150));
assert_eq!(context.error, None);
}
#[test_log::test]
fn test_http_event_context_with_error() {
let context = HttpEventContext {
url: "https://example.com".to_string(),
method: "POST".to_string(),
status: None,
headers: None,
duration_ms: None,
error: Some("Network timeout".to_string()),
};
assert_eq!(context.error, Some("Network timeout".to_string()));
assert_eq!(context.status, None);
}
#[test_log::test]
fn test_action_type_visibility_class() {
let action = ActionType::hide_class("my-class");
match action {
ActionType::Style { target, action } => {
assert_eq!(target, ElementTarget::Class(Target::from("my-class")));
assert_eq!(action, StyleAction::SetVisibility(Visibility::Hidden));
}
_ => panic!("Expected Style action"),
}
}
#[test_log::test]
fn test_action_type_display_class() {
let action = ActionType::display_class("my-class");
match action {
ActionType::Style { target, action } => {
assert_eq!(target, ElementTarget::Class(Target::from("my-class")));
assert_eq!(action, StyleAction::SetDisplay(true));
}
_ => panic!("Expected Style action"),
}
}
#[test_log::test]
fn test_action_type_background_last_child() {
let action = ActionType::set_background_last_child("blue");
match action {
ActionType::Style { target, action } => {
assert_eq!(target, ElementTarget::LastChild);
assert_eq!(action, StyleAction::SetBackground(Some("blue".to_string())));
}
_ => panic!("Expected Style action"),
}
}
#[test_log::test]
fn test_action_type_remove_background_last_child() {
let action = ActionType::remove_background_last_child();
match action {
ActionType::Style { target, action } => {
assert_eq!(target, ElementTarget::LastChild);
assert_eq!(action, StyleAction::SetBackground(None));
}
_ => panic!("Expected Style action"),
}
}
}