feat(editor-core): append_components_page_masters — design-kit masters on a hidden Components page, active design page stays clean

This commit is contained in:
Fini 2026-07-02 21:21:21 +08:00
parent 77f60a4efb
commit a67723fbc4

View file

@ -10,11 +10,18 @@
use crate::command_node::{build_leaf_node, remap_subtree_ids};
use crate::fills::set_primary_fill_hex;
use crate::node_id::NodeId;
use crate::pen_node_ext::PenNodeExt;
use crate::state::EditorState;
use crate::walkers;
use jian_ops_schema::node::PenNode;
use jian_ops_schema::page::PenPage;
/// Display name of the hidden master-store page that holds an imported
/// component library's reusable masters. It is a side store, not a
/// rendered design — kept off the active design page so scaffolding,
/// role/cleanup passes and page scoring never see the masters.
pub const COMPONENTS_PAGE_NAME: &str = "Components";
/// Build a bare page with id / name / children. State + lifecycle
/// default to `None`.
fn make_page(id: String, name: String, children: Vec<PenNode>) -> PenPage {
@ -126,6 +133,86 @@ impl EditorState {
Some(new_index)
}
/// Append the reusable masters of an imported component library
/// onto a dedicated, hidden [`COMPONENTS_PAGE_NAME`] page — NOT the
/// active design page. Keeps `active_children()` clean (only the
/// design) so the orchestrator's scaffold + role/cleanup passes are
/// unaffected, while the masters stay in `doc.pages` where the
/// document-wide component lookup (`ComponentLibrary::from_document`
/// + `ref_resolve::resolve_refs_for_canvas`) still finds them.
///
/// Master ids are preserved verbatim (NO remapping) so `ref` nodes
/// keep resolving to their targets. Masters are deduped by id
/// against whatever already lives on the components page, so a
/// re-import is idempotent.
///
/// Returns the number of masters actually appended (post-dedup).
/// The active page index is preserved: a single-page document is
/// first migrated so its design becomes page 0, and the components
/// page is appended after it, so the caller's active page keeps
/// pointing at the design.
pub fn append_components_page_masters(&mut self, masters: Vec<PenNode>) -> usize {
if masters.is_empty() {
return 0;
}
// Preserve the active design page across the migration: a
// single-page document moves its `doc.children` into page 0,
// and the components page is appended at the end.
let active = self.ui.active_page_index;
self.ensure_pages();
// Find (or create) the dedicated components page.
let page_idx = match self
.doc
.pages
.as_ref()
.unwrap()
.iter()
.position(|p| p.name == COMPONENTS_PAGE_NAME)
{
Some(idx) => idx,
None => {
// Mint a non-colliding page id without disturbing the
// master ids (which must stay verbatim for refs).
let page_id = self
.max_node_id()
.checked_add(1)
.map(|n| format!("n{n}"))
.unwrap_or_else(|| "components-page".to_string());
let pages = self.doc.pages.as_mut().unwrap();
pages.push(make_page(
page_id,
COMPONENTS_PAGE_NAME.to_string(),
Vec::new(),
));
pages.len() - 1
}
};
let pages = self.doc.pages.as_mut().unwrap();
let page = &mut pages[page_idx];
let mut existing: std::collections::HashSet<String> = page
.children
.iter()
.map(|n| n.id_str().to_string())
.collect();
let mut added = 0usize;
for master in masters {
let id = master.id_str().to_string();
if existing.contains(&id) {
continue;
}
existing.insert(id);
page.children.push(master);
added += 1;
}
// The components page is hidden side storage — never the active
// page. Restore the caller's active index (page 0 = design).
self.ui.active_page_index = active;
added
}
/// Duplicate the page at `idx`, inserting the clone after it.
/// New node ids are minted past `max_node_id`. Switches the
/// active page to the clone.