use crate::components::Component;
use crate::pack::PackId;
use crate::render::RenderRequest;
use crate::theme::Theme;
use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Debug, Clone)]
struct SectionContext {
heading: serde_json::Value,
items: Vec<serde_json::Value>,
}
#[derive(Debug, Clone)]
pub struct ReportBuilder {
template_id: String,
pack_id: Option<PackId>,
title: Option<String>,
subtitle: Option<String>,
theme: Option<Theme>,
components: Vec<serde_json::Value>,
assets: HashMap<String, PathBuf>,
metadata: HashMap<String, String>,
section_stack: Vec<SectionContext>,
}
impl ReportBuilder {
pub fn new(template_id: impl Into<String>) -> Self {
Self {
template_id: template_id.into(),
pack_id: None,
title: None,
subtitle: None,
theme: None,
components: Vec::new(),
assets: HashMap::new(),
metadata: HashMap::new(),
section_stack: Vec::new(),
}
}
pub fn pack(mut self, pack_id: impl Into<String>) -> Self {
self.pack_id = Some(PackId::new(pack_id));
self
}
pub fn title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
pub fn subtitle(mut self, subtitle: impl Into<String>) -> Self {
self.subtitle = Some(subtitle.into());
self
}
pub fn theme(mut self, theme: Theme) -> Self {
self.theme = Some(theme);
self
}
pub fn add_component(mut self, component: impl Component) -> Self {
let value = serde_json::json!({
"type": component.component_id(),
"data": component.to_data()
});
self.push_to_current_scope(value);
self
}
pub fn add_raw_component(mut self, component: serde_json::Value) -> Self {
self.push_to_current_scope(component);
self
}
pub fn add_components(mut self, components: impl IntoIterator<Item = impl Component>) -> Self {
for component in components {
let value = serde_json::json!({
"type": component.component_id(),
"data": component.to_data()
});
self.push_to_current_scope(value);
}
self
}
pub fn section(mut self, title: impl Into<String>, level: u8) -> Self {
let heading = serde_json::json!({
"type": "section",
"data": {
"title": title.into(),
"level": level.clamp(1, 6),
"content": []
}
});
self.section_stack.push(SectionContext {
heading,
items: Vec::new(),
});
self
}
pub fn end_section(mut self) -> Self {
let ctx = self
.section_stack
.pop()
.expect("end_section called without a matching section()");
let mut items = vec![ctx.heading];
items.extend(ctx.items);
let flow_group = serde_json::json!({
"type": "flow-group",
"data": {
"items": items,
"spacing": null,
"keep_together_if_under": null
}
});
self.push_to_current_scope(flow_group);
self
}
fn push_to_current_scope(&mut self, value: serde_json::Value) {
if let Some(ctx) = self.section_stack.last_mut() {
ctx.items.push(value);
} else {
self.components.push(value);
}
}
pub fn asset(mut self, name: impl Into<String>, path: impl Into<PathBuf>) -> Self {
self.assets.insert(name.into(), path.into());
self
}
pub fn metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.metadata.insert(key.into(), value.into());
self
}
pub fn build(self) -> RenderRequest {
RenderRequest {
template_id: self.template_id,
pack_id: self.pack_id,
title: self.title,
subtitle: self.subtitle,
theme: self.theme,
components: self.components,
assets: self.assets,
metadata: self.metadata,
}
}
}