mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-25 10:56:48 -06:00
* feat(dev-mode): Leptos code for a layer tree (#223) inspect.code takes language: leptos, and the Inspect panel lists it: the HTML markup inside a #[component] fn's view!, with the stylesheet after it as for React. Text is Rust string literals (line breaks as <br/>) so the macro parses it; px or rem like the other web languages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(dev-mode): Leptos components take their properties, instances are calls (#223) Leptos joins React in component code: a component or set is a #[component] fn with #[prop] defaults (variant enums with as_str, bool, String, ViewFn for instance swaps, Option<ViewFn> for slots), layers bound to booleans show through .then, variants differ by data-* CSS rules, and instances (also inside layer trees) are calls passing the values they change. Names are snake_case, Rust keywords get a trailing _. Code Connect takes leptos too (use <path>::<Name>; view! markup). Markup now takes the language instead of jsx/leptos flags; React and Leptos share the stylesheet pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Justin John <babyjjbabyjj549@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: bflatastic <bombayapirat@gmail.com>
80 lines
4.4 KiB
Rust
80 lines
4.4 KiB
Rust
//! Code for whole frames: nested HTML with a stylesheet (flexbox for auto layout, absolute
|
|
//! positions elsewhere), unique class names, escaped text.
|
|
|
|
use serde_json::{Value, json};
|
|
|
|
use crate::Session;
|
|
use crate::codegen::{CodeLang, code_for};
|
|
use crate::params::id_of;
|
|
|
|
fn id(v: Value) -> figcraft_doc::NodeId {
|
|
id_of(&v["id"]).unwrap()
|
|
}
|
|
|
|
#[test]
|
|
fn frames_become_nested_html_with_flexbox_and_absolute_children() {
|
|
let mut s = Session::new();
|
|
let card = id(s.execute("frame.create", &json!({"x": 0, "y": 0, "width": 200, "height": 120})).unwrap());
|
|
s.execute("node.set", &json!({"id": card, "props": {"name": "Card"}})).unwrap();
|
|
let a = id(s.execute("shape.rectangle", &json!({"x": 0, "y": 0, "width": 50, "height": 20, "parent": card})).unwrap());
|
|
let b = id(s.execute("shape.rectangle", &json!({"x": 0, "y": 30, "width": 50, "height": 20, "parent": card})).unwrap());
|
|
for r in [a, b] {
|
|
s.execute("node.set", &json!({"id": r, "props": {"name": "Row", "fill": "#0D99FF"}})).unwrap();
|
|
}
|
|
let t = id(s.execute("text.create", &json!({"text": "Hi & <bye>", "x": 0, "y": 60})).unwrap());
|
|
s.execute("node.reparent", &json!({"ids": [t], "parent": card})).unwrap();
|
|
s.execute("layout.add", &json!({"ids": [card]})).unwrap();
|
|
s.execute("node.set", &json!({"id": card, "props": {"layoutMode": "vertical", "gap": 8}})).unwrap();
|
|
|
|
let code = code_for(s.doc().unwrap(), card, CodeLang::Html);
|
|
assert!(code.starts_with("<div class=\"card\">"), "{code}");
|
|
assert!(code.contains("<div class=\"row\"></div>") && code.contains("<div class=\"row-2\"></div>"), "unique classes: {code}");
|
|
assert!(code.contains("& <bye></p>"), "escaped text: {code}");
|
|
assert!(code.contains(".card {\n position: relative;\n") && code.contains("display: flex;") && code.contains("flex-direction: column;"));
|
|
assert!(!code.contains(".row {\n position: absolute"), "flex items flow");
|
|
assert!(code.trim_end().ends_with("</style>"));
|
|
|
|
// Without auto layout, children sit at their positions.
|
|
s.execute("layout.remove", &json!({"ids": [card]})).unwrap();
|
|
let code = code_for(s.doc().unwrap(), card, CodeLang::Html);
|
|
assert!(code.contains(".row-2 {\n position: absolute;\n left: "), "{code}");
|
|
// The command takes the language by id.
|
|
let out = s.execute("inspect.code", &json!({"id": card, "language": "html"})).unwrap();
|
|
assert!(out["code"].as_str().unwrap().contains("<style>"));
|
|
}
|
|
|
|
#[test]
|
|
fn class_names_are_valid_and_deep_trees_are_bounded() {
|
|
let mut s = Session::new();
|
|
let mut parent = id(s.execute("frame.create", &json!({"x": 0, "y": 0, "width": 100, "height": 100})).unwrap());
|
|
s.execute("node.set", &json!({"id": parent, "props": {"name": "1 Ünïcode / Name!!"}})).unwrap();
|
|
let root = parent;
|
|
for _ in 0..30 {
|
|
parent = id(s.execute("frame.create", &json!({"x": 1, "y": 1, "width": 90, "height": 90, "parent": parent})).unwrap());
|
|
}
|
|
let code = code_for(s.doc().unwrap(), root, CodeLang::Html);
|
|
assert!(code.starts_with("<div class=\"layer-1-n-code-name\">"), "{}", code.lines().next().unwrap());
|
|
// Nesting stops at the depth limit instead of running away.
|
|
assert!(code.matches("<div").count() <= 14, "{}", code.matches("<div").count());
|
|
}
|
|
|
|
#[test]
|
|
fn leptos_wraps_the_markup_in_a_component_with_text_as_string_literals() {
|
|
let mut s = Session::new();
|
|
let card = id(s.execute("frame.create", &json!({"x": 0, "y": 0, "width": 200, "height": 120})).unwrap());
|
|
s.execute("node.set", &json!({"id": card, "props": {"name": "Card"}})).unwrap();
|
|
let t = id(s.execute("text.create", &json!({"text": "Say \"hi\" {x}\nسلام", "x": 0, "y": 60})).unwrap());
|
|
s.execute("node.reparent", &json!({"ids": [t], "parent": card})).unwrap();
|
|
|
|
let code = code_for(s.doc().unwrap(), card, CodeLang::Leptos);
|
|
assert!(
|
|
code.starts_with("use leptos::prelude::*;\n\n#[component]\npub fn Card() -> impl IntoView {\n view! {\n <div class=\"card\">"),
|
|
"{code}"
|
|
);
|
|
// Quotes and braces stay inside Rust string literals; lines break with a self-closed tag.
|
|
assert!(code.contains(r#">"Say \"hi\" {x}"<br/>"سلام"</p>"#), "{code}");
|
|
assert!(code.contains("/* card.css */\n") && code.contains(".card {\n position: relative;\n"), "{code}");
|
|
let out = s.execute("inspect.code", &json!({"id": card, "language": "leptos", "unit": "rem"})).unwrap();
|
|
assert!(out["code"].as_str().unwrap().contains("width: 12.5rem;"), "{}", out["code"]);
|
|
}
|