use super::Component;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct List {
#[serde(default)]
pub title: Option<String>,
pub items: Vec<ListItem>,
#[serde(default = "default_list_layout")]
pub layout: String,
#[serde(default = "default_columns")]
pub columns: usize,
#[serde(default)]
pub numbered: bool,
}
fn default_list_layout() -> String {
"vertical".into()
}
fn default_columns() -> usize {
1
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListItem {
pub content: String,
#[serde(default)]
pub icon: Option<String>,
#[serde(default)]
pub children: Vec<ListItem>,
}
impl Default for List {
fn default() -> Self {
Self::new()
}
}
impl List {
pub fn new() -> Self {
Self {
title: None,
items: Vec::new(),
layout: "vertical".into(),
columns: 1,
numbered: false,
}
}
pub fn with_title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
pub fn add_item(mut self, content: impl Into<String>) -> Self {
self.items.push(ListItem {
content: content.into(),
icon: None,
children: Vec::new(),
});
self
}
pub fn add_item_with_icon(
mut self,
content: impl Into<String>,
icon: impl Into<String>,
) -> Self {
self.items.push(ListItem {
content: content.into(),
icon: Some(icon.into()),
children: Vec::new(),
});
self
}
pub fn grid_layout(mut self, columns: usize) -> Self {
self.layout = "grid".into();
self.columns = columns;
self
}
pub fn numbered(mut self) -> Self {
self.numbered = true;
self
}
}
impl Component for List {
fn component_id(&self) -> &'static str {
"list"
}
fn to_data(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Divider {
#[serde(default = "default_divider_style")]
pub style: String,
#[serde(default = "default_divider_thickness")]
pub thickness: String,
#[serde(default)]
pub color: Option<String>,
#[serde(default = "default_spacing")]
pub spacing_above: String,
#[serde(default = "default_spacing")]
pub spacing_below: String,
}
fn default_divider_style() -> String {
"solid".into()
}
fn default_divider_thickness() -> String {
"0.5pt".into()
}
fn default_spacing() -> String {
"12pt".into()
}
impl Default for Divider {
fn default() -> Self {
Self::new()
}
}
impl Divider {
pub fn new() -> Self {
Self {
style: "solid".into(),
thickness: "0.5pt".into(),
color: None,
spacing_above: "12pt".into(),
spacing_below: "12pt".into(),
}
}
pub fn dashed() -> Self {
Self {
style: "dashed".into(),
..Self::new()
}
}
pub fn dotted() -> Self {
Self {
style: "dotted".into(),
..Self::new()
}
}
pub fn thick() -> Self {
Self {
thickness: "2pt".into(),
..Self::new()
}
}
pub fn with_color(mut self, color: impl Into<String>) -> Self {
self.color = Some(color.into());
self
}
}
impl Component for Divider {
fn component_id(&self) -> &'static str {
"divider"
}
fn to_data(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Grid {
#[serde(default)]
pub title: Option<String>,
pub columns: usize,
pub items: Vec<GridItem>,
#[serde(default = "default_grid_gap")]
pub column_gap: String,
#[serde(default = "default_grid_gap")]
pub row_gap: String,
#[serde(default)]
pub item_min_height: Option<String>,
}
fn default_grid_gap() -> String {
"16pt".into()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GridItem {
pub content: serde_json::Value,
#[serde(default = "default_span")]
pub colspan: usize,
}
fn default_span() -> usize {
1
}
impl Grid {
pub fn new(columns: usize) -> Self {
Self {
title: None,
columns,
items: Vec::new(),
column_gap: "16pt".into(),
row_gap: "16pt".into(),
item_min_height: None,
}
}
pub fn with_item_min_height(mut self, min_height: impl Into<String>) -> Self {
self.item_min_height = Some(min_height.into());
self
}
pub fn add_item(mut self, content: serde_json::Value) -> Self {
self.items.push(GridItem {
content,
colspan: 1,
});
self
}
pub fn add_item_with_span(mut self, content: serde_json::Value, colspan: usize) -> Self {
self.items.push(GridItem { content, colspan });
self
}
}
impl Component for Grid {
fn component_id(&self) -> &'static str {
"grid-component"
}
fn to_data(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FlowGroup {
pub items: Vec<serde_json::Value>,
#[serde(default)]
pub spacing: Option<String>,
#[serde(default)]
pub keep_together_if_under: Option<String>,
}
impl Default for FlowGroup {
fn default() -> Self {
Self::new()
}
}
impl FlowGroup {
pub fn new() -> Self {
Self {
items: Vec::new(),
spacing: None,
keep_together_if_under: None,
}
}
pub fn add_item(mut self, content: serde_json::Value) -> Self {
self.items.push(content);
self
}
pub fn with_spacing(mut self, spacing: impl Into<String>) -> Self {
self.spacing = Some(spacing.into());
self
}
pub fn with_keep_together_if_under(mut self, threshold: impl Into<String>) -> Self {
self.keep_together_if_under = Some(threshold.into());
self
}
}
impl Component for FlowGroup {
fn component_id(&self) -> &'static str {
"flow-group"
}
fn to_data(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PageBreak;
impl Default for PageBreak {
fn default() -> Self {
Self::new()
}
}
impl PageBreak {
pub fn new() -> Self {
Self
}
}
impl Component for PageBreak {
fn component_id(&self) -> &'static str {
"page-break"
}
fn to_data(&self) -> serde_json::Value {
serde_json::json!({})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Watermark {
pub text: String,
#[serde(default = "default_rotation")]
pub rotation: f64,
#[serde(default = "default_opacity")]
pub opacity: f64,
#[serde(default = "default_watermark_size")]
pub size: String,
}
fn default_rotation() -> f64 {
-45.0
}
fn default_opacity() -> f64 {
0.1
}
fn default_watermark_size() -> String {
"48pt".into()
}
impl Watermark {
pub fn new(text: impl Into<String>) -> Self {
Self {
text: text.into(),
rotation: -45.0,
opacity: 0.1,
size: "48pt".into(),
}
}
pub fn confidential() -> Self {
Self::new("CONFIDENTIAL")
}
pub fn draft() -> Self {
Self::new("DRAFT")
}
pub fn with_rotation(mut self, degrees: f64) -> Self {
self.rotation = degrees;
self
}
pub fn with_opacity(mut self, opacity: f64) -> Self {
self.opacity = opacity.clamp(0.0, 1.0);
self
}
}
impl Component for Watermark {
fn component_id(&self) -> &'static str {
"watermark"
}
fn to_data(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProgressBar {
pub label: String,
pub value: f64,
#[serde(default = "default_max")]
pub max: f64,
#[serde(default = "default_true")]
pub show_percentage: bool,
#[serde(default)]
pub color: Option<String>,
}
fn default_max() -> f64 {
100.0
}
fn default_true() -> bool {
true
}
impl ProgressBar {
pub fn new(label: impl Into<String>, value: f64) -> Self {
Self {
label: label.into(),
value,
max: 100.0,
show_percentage: true,
color: None,
}
}
pub fn with_max(mut self, max: f64) -> Self {
self.max = max;
self
}
pub fn with_color(mut self, color: impl Into<String>) -> Self {
self.color = Some(color.into());
self
}
pub fn percentage(&self) -> f64 {
(self.value / self.max * 100.0).min(100.0)
}
}
impl Component for ProgressBar {
fn component_id(&self) -> &'static str {
"progress-bar"
}
fn to_data(&self) -> serde_json::Value {
let mut data = serde_json::to_value(self).unwrap_or_default();
if let serde_json::Value::Object(ref mut map) = data {
map.insert("percentage".into(), serde_json::json!(self.percentage()));
}
data
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KeyValueList {
#[serde(default)]
pub title: Option<String>,
pub items: Vec<KeyValuePair>,
#[serde(default = "default_kv_layout")]
pub layout: String,
}
fn default_kv_layout() -> String {
"vertical".into()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KeyValuePair {
pub key: String,
pub value: String,
#[serde(default)]
pub highlight: bool,
}
impl Default for KeyValueList {
fn default() -> Self {
Self::new()
}
}
impl KeyValueList {
pub fn new() -> Self {
Self {
title: None,
items: Vec::new(),
layout: "vertical".into(),
}
}
pub fn with_title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
pub fn add(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.items.push(KeyValuePair {
key: key.into(),
value: value.into(),
highlight: false,
});
self
}
pub fn add_highlighted(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.items.push(KeyValuePair {
key: key.into(),
value: value.into(),
highlight: true,
});
self
}
}
impl Component for KeyValueList {
fn component_id(&self) -> &'static str {
"key-value-list"
}
fn to_data(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TableOfContents {
pub title: String,
pub depth: u8,
#[serde(skip_serializing_if = "Option::is_none")]
pub font_size: Option<String>,
}
impl TableOfContents {
pub fn new() -> Self {
Self {
title: "Inhaltsverzeichnis".to_string(),
depth: 3,
font_size: None,
}
}
pub fn with_title(mut self, title: impl Into<String>) -> Self {
self.title = title.into();
self
}
pub fn with_depth(mut self, depth: u8) -> Self {
self.depth = depth;
self
}
pub fn with_font_size(mut self, size: impl Into<String>) -> Self {
self.font_size = Some(size.into());
self
}
}
impl Default for TableOfContents {
fn default() -> Self {
Self::new()
}
}
impl Component for TableOfContents {
fn component_id(&self) -> &'static str {
"table-of-contents"
}
fn to_data(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or_default()
}
}