zenith_cli/library/
component.rs1use std::collections::BTreeMap;
4
5use zenith_core::{ComponentDef, Document, InstanceNode, LibraryDef, Node, ProvenanceDef};
6
7use super::add::{
8 AddError, AddOutcome, collect_all_ids, copy_assets, copy_styles, copy_tokens,
9 load_pack_document, px, target_component_id, unique_id, unknown_package_error,
10};
11use super::registry::LibraryPack;
12
13pub fn materialize(
38 target: &mut Document,
39 packs: &[LibraryPack],
40 pkg_id: &str,
41 item: &str,
42 page_id: &str,
43 id_base: &str,
44 at: (f64, f64),
45) -> Result<AddOutcome, AddError> {
46 let (at_x, at_y) = at;
47 let pack = packs
49 .iter()
50 .find(|p| p.id == pkg_id)
51 .ok_or_else(|| unknown_package_error(pkg_id, packs))?;
52
53 let pack_doc = load_pack_document(pack)?;
54
55 let comp = pack_doc
56 .components
57 .iter()
58 .find(|c| c.id == item)
59 .ok_or_else(|| {
60 let available: Vec<&str> = pack_doc.components.iter().map(|c| c.id.as_str()).collect();
61 AddError::new(format!(
62 "unknown item '{}' in package '{}' (available: {})",
63 item,
64 pkg_id,
65 if available.is_empty() {
66 "none".to_owned()
67 } else {
68 available.join(", ")
69 }
70 ))
71 })?;
72
73 if !target.body.pages.iter().any(|p| p.id == page_id) {
76 let available: Vec<&str> = target.body.pages.iter().map(|p| p.id.as_str()).collect();
77 return Err(AddError::new(format!(
78 "page '{}' not found in target document (available: {})",
79 page_id,
80 if available.is_empty() {
81 "none".to_owned()
82 } else {
83 available.join(", ")
84 }
85 )));
86 }
87
88 let mut warnings: Vec<String> = Vec::new();
89
90 let comp_id = target_component_id(pkg_id, item);
92 if !target.components.iter().any(|c| c.id == comp_id) {
93 target.components.push(ComponentDef {
94 id: comp_id.clone(),
95 children: comp.children.clone(),
96 source_span: None,
97 });
98 }
99
100 if target.tokens.format.is_empty() {
103 target.tokens.format = pack_doc.tokens.format.clone();
104 }
105 copy_tokens(
106 &pack_doc.tokens.tokens,
107 &mut target.tokens.tokens,
108 &mut warnings,
109 );
110 copy_styles(
111 &pack_doc.styles.styles,
112 &mut target.styles.styles,
113 &mut warnings,
114 );
115 copy_assets(
116 &pack_doc.assets.assets,
117 &mut target.assets.assets,
118 &mut warnings,
119 );
120
121 let mut all_ids = collect_all_ids(target);
123 let instance_id = unique_id(id_base, &all_ids);
124 all_ids.insert(instance_id.clone());
125
126 let instance = InstanceNode {
127 id: instance_id.clone(),
128 name: None,
129 role: None,
130 component: comp_id.clone(),
131 x: Some(px(at_x)),
132 y: Some(px(at_y)),
133 opacity: None,
134 visible: None,
135 locked: None,
136 overrides: Vec::new(),
137 source_span: None,
138 unknown_props: BTreeMap::new(),
139 };
140
141 if let Some(page) = target.body.pages.iter_mut().find(|p| p.id == page_id) {
144 page.children.push(Node::Instance(instance));
145 }
146
147 let provenance_id = unique_id(&format!("prov.{}", instance_id), &all_ids);
149
150 if !target.libraries.iter().any(|l| l.id == pkg_id) {
151 target.libraries.push(LibraryDef {
152 id: pkg_id.to_owned(),
153 version: pack.version.clone(),
154 hash: None,
155 source_span: None,
156 unknown_props: BTreeMap::new(),
157 });
158 }
159
160 target.provenance.push(ProvenanceDef {
161 id: provenance_id.clone(),
162 node: instance_id.clone(),
163 library: pkg_id.to_owned(),
164 item: Some(item.to_owned()),
165 linked: Some(true),
166 source_span: None,
167 unknown_props: BTreeMap::new(),
168 });
169
170 Ok(AddOutcome {
171 pkg_id: pkg_id.to_owned(),
172 item: item.to_owned(),
173 target_component_id: comp_id,
174 instance_id,
175 provenance_id,
176 warnings,
177 })
178}