feat(shell-core/figma): kind→NodeKind mapper (clipboard JSON one step from real import)

`FigmaClipboardNode::to_node_kind()` maps the 8 most-common Figma
type strings to the closest `crate::document::NodeKind`:

  RECTANGLE              → Rect
  ELLIPSE                → Ellipse
  LINE                   → Line
  POLYGON / REGULAR_POLY → Polygon
  VECTOR                 → Path
  TEXT                   → Text
  FRAME / SECTION        → Frame
  GROUP                  → Group
  <unknown>              → Other(<kind string>)

Unknown kinds round-trip via `NodeKind::Other(...)` so the layer
panel still renders something honest instead of dropping the node.
Future commits add per-kind specialised geometry mappers (fills /
strokes / vectors / text / layout) ported from `pen-figma`.

Tests (2 new):
  - 10-case table of every supported Figma kind → expected NodeKind
  - Unknown `BOOLEAN_OPERATION` → `Other("BOOLEAN_OPERATION")` round-trip

#9 Figma now ~50% — file kind detection + clipboard JSON walker +
per-child kind + name + NodeKind mapping. Remaining: per-kind
geometry/style mapper functions (the `pen-figma` figma-fill-mapper /
stroke-mapper / text-mapper / vector-decoder / etc port).

Tests total: 245 shell-core (+2). Wasm32 build clean.
This commit is contained in:
Kayshen-X 2026-05-14 15:50:56 +08:00
parent d97e1b809d
commit cf48a006de

View file

@ -222,6 +222,36 @@ pub struct FigmaClipboardNode {
pub name: String,
}
impl FigmaClipboardNode {
/// Map this clipboard node's Figma kind string onto the
/// closest `NodeKind` in the shell-core document model.
/// Unknown kinds round-trip as `NodeKind::Other(kind)` so the
/// shell still has a layer-panel entry to display.
/// Covers the 8 most-common Figma kinds — the remaining
/// specialised types (BOOLEAN_OPERATION, SLICE, COMPONENT,
/// INSTANCE, STAR, ...) ship in follow-up commits as the
/// converters need them.
pub fn to_node_kind(&self) -> crate::document::NodeKind {
use crate::document::NodeKind;
match self.kind.as_str() {
"RECTANGLE" => NodeKind::Rect,
"ELLIPSE" => NodeKind::Ellipse,
"LINE" => NodeKind::Line,
"POLYGON" | "REGULAR_POLYGON" => NodeKind::Polygon,
"VECTOR" => NodeKind::Path,
"TEXT" => NodeKind::Text,
"FRAME" | "GROUP" | "SECTION" => {
if self.kind == "GROUP" {
NodeKind::Group
} else {
NodeKind::Frame
}
}
_ => NodeKind::Other(self.kind.clone()),
}
}
}
/// Successful parse result. Carries the file kind, top-level
/// children count, and (for clipboard JSON) a minimal-fields
/// extraction of each top-level child. Placeholder until the full
@ -327,6 +357,43 @@ mod tests {
assert_eq!(parsed.clipboard_nodes[1].name, "");
}
#[test]
fn figma_kind_maps_to_node_kind_for_common_types() {
use crate::document::NodeKind;
let cases = [
("RECTANGLE", NodeKind::Rect),
("ELLIPSE", NodeKind::Ellipse),
("LINE", NodeKind::Line),
("POLYGON", NodeKind::Polygon),
("REGULAR_POLYGON", NodeKind::Polygon),
("VECTOR", NodeKind::Path),
("TEXT", NodeKind::Text),
("FRAME", NodeKind::Frame),
("GROUP", NodeKind::Group),
("SECTION", NodeKind::Frame),
];
for (input, expected) in cases {
let n = FigmaClipboardNode {
kind: input.into(),
name: String::new(),
};
assert_eq!(n.to_node_kind(), expected, "{input}");
}
}
#[test]
fn figma_kind_unknown_round_trips_via_other() {
use crate::document::NodeKind;
let n = FigmaClipboardNode {
kind: "BOOLEAN_OPERATION".into(),
name: String::new(),
};
assert_eq!(
n.to_node_kind(),
NodeKind::Other("BOOLEAN_OPERATION".into())
);
}
#[test]
fn parse_fig_returns_unknown_for_random_bytes() {
assert_eq!(parse_fig(b"random"), Err(FigParseError::UnknownFormat));