app
Adom Symbol
Public Made by Adomby adom
KiCad symbol creator with interactive viewer and delivery to KiCad/Fusion 360
← 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
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")) }