← Commit history

1.1.3: embed viewer matches the app — 3D outline in body, grid bg, bottom info bar, pin-panel title, in-canvas zoom toolbar

John Lauer ·e322aae8b1 ·3mo ago ·parent bad2e7a
6 files changed +59−15
Cargo.toml+1−1
@@ -1,6 +1,6 @@ [package] name = "adom-symbol"-version = "1.1.2"+version = "1.1.3" edition = "2021" description = "Create and preview KiCad/Fusion 360 schematic symbols with interactive viewer" 
VERSION+1−1
@@ -1 +1 @@-1.1.2+1.1.3
bin/adom-symbol
⋯ 1 unchanged line ⋯
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "adom-symbol",-  "version": "1.1.2",+  "version": "1.1.3",   "type": "app",   "description": "KiCad symbol creator with interactive viewer and delivery to KiCad/Fusion 360",   "dependencies": {
src/embed.html+26−11
@@ -29,10 +29,11 @@   header .subject-meta{display:flex;align-items:center;gap:6px;color:var(--text2);font-family:'JetBrains Mono',ui-monospace,monospace;font-size:11px;white-space:nowrap}   header .subject-meta .sep{color:var(--text3)}   @media(max-width:720px){header .subject-meta{display:none}}-  header .actions{display:flex;align-items:center;gap:6px;margin-left:auto;flex-shrink:0}-  header .actions .hbtn{width:28px;height:28px;display:grid;place-items:center;background:transparent;border:1px solid transparent;border-radius:6px;color:var(--text2);cursor:pointer;transition:color .15s ease,border-color .15s ease}-  header .actions .hbtn:hover{color:var(--accent-bright);border-color:var(--border)}-  header .actions .hbtn svg{width:15px;height:15px;display:block}+  /* Floating in-canvas viewer toolbar (frosted, matches the app's glass cards) */+  .tools{position:absolute;left:12px;top:12px;z-index:5;display:flex;gap:3px;padding:3px;background:rgba(22,27,34,.82);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:1px solid var(--border);border-radius:9px}+  .tools button{width:30px;height:30px;display:grid;place-items:center;background:transparent;border:none;border-radius:6px;color:var(--text2);cursor:pointer;transition:background .15s ease,color .15s ease}+  .tools button:hover{background:var(--elevated);color:var(--accent-bright)}+  .tools button svg{width:16px;height:16px;display:block}   header .hover-popover{position:absolute;top:calc(100% + 6px);left:16px;background:var(--elevated);border:1px solid var(--border);border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,.5);padding:12px 14px;min-width:300px;max-width:420px;font-family:'Satoshi',sans-serif;font-size:12px;color:var(--text);z-index:99999;opacity:0;transform:translateY(-2px);pointer-events:none;transition:opacity .12s ease,transform .12s ease}   header .hover-popover.show{opacity:1;transform:none;pointer-events:auto}   header .hover-popover .popover-title{font-family:'Familjen Grotesk',sans-serif;font-size:14px;font-weight:700;color:var(--text);margin-bottom:8px}@@ -44,8 +45,11 @@   header .hover-popover .popover-link:hover{text-decoration:underline}   main{flex:1;display:flex;min-height:0}   .vpwrap{flex:1;position:relative;min-width:0}+  /* Schematic-workbench grid, identical to the main app's #stage. */   .vp{position:absolute;inset:0;overflow:hidden;cursor:grab;background:-      radial-gradient(circle at 50% 40%, #121a26 0%, #0d1117 70%)}+      radial-gradient(120% 120% at 50% 30%,rgba(0,184,177,.05),transparent 60%),+      linear-gradient(rgba(255,255,255,.025) 1px,transparent 1px) 0 0/22px 22px,+      linear-gradient(90deg,rgba(255,255,255,.025) 1px,transparent 1px) 0 0/22px 22px}   .vp.grab{cursor:grabbing}   /* Only the DIRECT-CHILD symbol svg gets the pan/zoom transform — scoping to      `.vp > svg` (not `.vp svg`) keeps it off any nested icon svgs. */@@ -67,6 +71,16 @@   .pin.on .d{display:block}   .empty{color:var(--muted);font-size:12px;padding:14px}   @media(max-width:620px){aside{flex-basis:200px}}+  /* pin panel header — reads like a studio section */+  aside .ah .atitle{font-family:'Familjen Grotesk',sans-serif;font-weight:600;font-size:12px;color:var(--text);white-space:nowrap}+  aside .ah .acount{margin-left:5px;font-family:'JetBrains Mono',ui-monospace,monospace;font-size:11px;color:var(--text3)}+  /* bottom info bar — mirrors the main app footer */+  footer{flex:0 0 auto;display:flex;align-items:center;height:26px;border-top:1px solid var(--border);background:var(--surface);font-size:11px;color:var(--text2);padding:0 14px;overflow:hidden}+  footer .it{display:flex;align-items:center;gap:6px;white-space:nowrap;min-width:0}+  footer .it+.it::before{content:"";width:1px;height:13px;background:var(--border);margin:0 12px;flex:0 0 1px}+  footer .k{color:var(--text3);text-transform:uppercase;font-size:9px;letter-spacing:.4px;flex:0 0 auto}+  footer .v{color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}+  footer .descit{min-width:0;overflow:hidden}   /* pin hover — highlight + tooltip, parity with the live SymView */   .vp > svg text,.vp > svg g.stroked-text{pointer-events:none}   .hit{fill:rgba(0,230,220,0);stroke:none;cursor:pointer}@@ -94,11 +108,6 @@       <span class="subject-name">__NAME__</span>       <span class="subject-meta">__META__</span>     </div>-    <div class="actions">-      <button id="zout" class="hbtn" title="Zoom out" aria-label="Zoom out"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="10.5" cy="10.5" r="6.5"/><path d="M20 20l-4.5-4.5M8 10.5h5"/></svg></button>-      <button id="zin" class="hbtn" title="Zoom in" aria-label="Zoom in"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="10.5" cy="10.5" r="6.5"/><path d="M20 20l-4.5-4.5M10.5 8v5M8 10.5h5"/></svg></button>-      <button id="fit" class="hbtn" title="Fit to window" aria-label="Fit to window"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 9V5a1 1 0 0 1 1-1h4M20 9V5a1 1 0 0 0-1-1h-4M4 15v4a1 1 0 0 0 1 1h4M20 15v4a1 1 0 0 1-1 1h-4"/></svg></button>-    </div>     <div class="hover-popover" id="app-info" role="dialog" aria-label="About adom-symbol">       <div class="popover-title">adom-symbol</div>       <div class="popover-row"><span class="k">about</span><span class="v">__ABOUT__</span></div>@@ -114,15 +123,21 @@   <main>     <div class="vpwrap">       <div class="vp" id="vp">+        <div class="tools">+          <button id="zout" title="Zoom out" aria-label="Zoom out"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="10.5" cy="10.5" r="6.5"/><path d="M20 20l-4.5-4.5M8 10.5h5"/></svg></button>+          <button id="zin" title="Zoom in" aria-label="Zoom in"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="10.5" cy="10.5" r="6.5"/><path d="M20 20l-4.5-4.5M10.5 8v5M8 10.5h5"/></svg></button>+          <button id="fit" title="Fit to window" aria-label="Fit to window"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 9V5a1 1 0 0 1 1-1h4M20 9V5a1 1 0 0 0-1-1h-4M4 15v4a1 1 0 0 0 1 1h4M20 15v4a1 1 0 0 1-1 1h-4"/></svg></button>+        </div>         <div class="hint">drag to pan · scroll to zoom</div>         __SVG__       </div>     </div>     <aside>-      <div class="ah"><input id="q" placeholder="Filter pins…"></div>+      <div class="ah"><span class="atitle">Pins <span class="acount">__PINS_N__</span></span><input id="q" placeholder="Filter…"></div>       <div class="pins" id="pins"></div>     </aside>   </main>+  <footer>__FOOTER__</footer>   <div id="tip"></div> <script> var PINS=__PINS__, OFF=__OFFSET__;
src/main.rs+30−1
@@ -430,7 +430,17 @@ fn cmd_ui(action: String, args: Vec<String>, port: u16) -> Result<()> { fn cmd_embed(file: PathBuf, name: Option<String>, out: Option<PathBuf>) -> Result<()> {     let content = fs::read_to_string(&file).with_context(|| format!("reading {}", file.display()))?;     let symbol_name = name.unwrap_or_else(|| first_symbol_name(&content).unwrap_or_else(|| "symbol".to_string()));-    let svg = render::render_symbol_svg(&content, &symbol_name)?;+    let mut svg = render::render_symbol_svg(&content, &symbol_name)?;+    // Bake the 3D-chip outline into the symbol body — the signature look the live+    // app and `render` show — when the sibling white outline asset exists.+    let outline = default_white_outline(&file);+    if let Some(p) = outline.as_ref() {+        let frac = render::chip_size_frac("large");+        match render::composite_iso_into_symbol(&svg, &content, &symbol_name, p, frac) {+            Ok(s) => svg = s,+            Err(e) => eprintln!("WARN: 3D-chip composite skipped: {e}"),+        }+    }     let meta = parse::parse_kicad_sym(&content, &symbol_name);     let html = build_embed_html(&symbol_name, &svg, &meta);     let out = out.unwrap_or_else(|| {@@ -480,6 +490,23 @@ pub(crate) fn build_embed_html(name: &str, svg: &str, meta: &parse::SymbolMeta)     } else {         format!("<div class=\"popover-row\"><span class=\"k\">mfr</span><span class=\"v\">{}</span></div>", esc(mfr))     };+    // Bottom info bar items (MFR · PKG · PINS · DESC) — mirrors the live app footer.+    let mut foot = String::new();+    for (k, v) in [("MFR", mfr), ("PKG", pkg)] {+        if !v.is_empty() {+            foot.push_str(&format!("<span class=\"it\"><span class=\"k\">{k}</span><span class=\"v\">{}</span></span>", esc(v)));+        }+    }+    foot.push_str(&format!("<span class=\"it\"><span class=\"k\">PINS</span><span class=\"v\">{}</span></span>", meta.pins.len()));+    let desc = meta.description.trim();+    if !desc.is_empty() {+        let d: String = if desc.chars().count() > 120 {+            format!("{}…", desc.chars().take(120).collect::<String>())+        } else {+            desc.to_string()+        };+        foot.push_str(&format!("<span class=\"it descit\"><span class=\"k\">DESC</span><span class=\"v\">{}</span></span>", esc(&d)));+    }     include_str!("embed.html")         .replace("__NAME__", &esc(name))         .replace("__SVG__", svg)@@ -488,6 +515,8 @@ pub(crate) fn build_embed_html(name: &str, svg: &str, meta: &parse::SymbolMeta)         .replace("__META__", &subj_meta)         .replace("__ABOUT__", about)         .replace("__MFR_ROW__", &mfr_row)+        .replace("__FOOTER__", &foot)+        .replace("__PINS_N__", &meta.pins.len().to_string())         .replace("__VERSION__", env!("CARGO_PKG_VERSION")) }