404 lines
22 KiB
HTML
404 lines
22 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<script>window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments)}gtag("consent","default",{ad_storage:"granted",ad_user_data:"granted",ad_personalization:"granted",analytics_storage:"granted"});gtag("consent","default",{ad_storage:"denied",ad_user_data:"denied",ad_personalization:"denied",analytics_storage:"denied",region:"AT,BE,BG,HR,CY,CZ,DK,EE,FI,FR,DE,GR,HU,IE,IT,LV,LT,LU,MT,NL,PL,PT,RO,SK,SI,ES,SE,IS,LI,NO,GB,CH,UA".split(",")});</script>
|
||
<!-- Google tag (gtag.js) -->
|
||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-X1MKL6C4B0"></script>
|
||
<script>window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments)}gtag("js",new Date());gtag("config","G-X1MKL6C4B0");</script>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Schematic ReTrace</title>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
|
||
<script src="db-core.js"></script>
|
||
<style>
|
||
@import url('https://fonts.googleapis.com/css2?family=Share+Tech+Mono&family=Rajdhani:wght@400;500;600;700&family=Space+Mono:wght@400;700&display=swap');
|
||
:root {
|
||
--bg:#0d1117; --surface:#161b22; --surface2:#1c2230; --border:#30363d;
|
||
--accent:#00e5a0; --accent2:#00a8ff; --accent3:#ff6b35; --warn:#f0c040;
|
||
--text:#e6edf3; --text2:#8b949e;
|
||
}
|
||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||
body {
|
||
background: var(--bg); color: var(--text);
|
||
font-family: 'Rajdhani', sans-serif; font-size: 14px;
|
||
height: 100dvh; display: flex; flex-direction: column; overflow: hidden;
|
||
}
|
||
|
||
/* ── Header ── */
|
||
#header {
|
||
background: var(--surface); border-bottom: 1px solid var(--border);
|
||
display: flex; align-items: center; gap: 12px; padding: 6px 14px; flex-shrink: 0;
|
||
}
|
||
#header .logo { font-family: 'Share Tech Mono', monospace; font-size: 18px; color: var(--accent); letter-spacing: 2px; white-space: nowrap; }
|
||
#header .logo span { color: var(--accent2); }
|
||
.sep { width: 1px; height: 24px; background: var(--border); flex-shrink: 0; }
|
||
.spacer { flex: 1; }
|
||
|
||
/* ── Buttons ── */
|
||
.btn {
|
||
background: var(--surface2); border: 1px solid var(--border); color: var(--text);
|
||
padding: 4px 12px; border-radius: 4px; cursor: pointer;
|
||
font-family: 'Rajdhani', sans-serif; font-size: 13px; font-weight: 600;
|
||
transition: all .15s; white-space: nowrap;
|
||
display: inline-flex; align-items: center; justify-content: center;
|
||
}
|
||
.btn:hover { border-color: var(--accent); color: var(--accent); }
|
||
.btn.accent { border-color: var(--accent); color: var(--accent); }
|
||
.btn.accent:hover { background: var(--accent); color: var(--bg); }
|
||
.btn.warn { border-color: var(--warn); color: var(--warn); }
|
||
.btn-group { display: flex; gap: 6px; align-items: center; }
|
||
|
||
/* ── Status ── */
|
||
#status-msg { font-family: 'Space Mono', monospace; font-size: 11px; color: var(--text2); max-width: 320px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||
#status-msg.ok { color: var(--accent); }
|
||
#status-msg.err { color: var(--accent3); }
|
||
#status-msg.warn { color: var(--warn); }
|
||
|
||
/* ── Main layout ── */
|
||
#main { display: flex; flex: 1; overflow: hidden; }
|
||
|
||
/* ── Left panel ── */
|
||
#left-panel { width: 220px; background: var(--surface); border-right: 1px solid var(--border); display: flex; flex-direction: column; flex-shrink: 0; overflow: hidden; }
|
||
.panel-title { padding: 8px 12px; font-size: 11px; font-weight: 700; letter-spacing: 2px; color: var(--text2); border-bottom: 1px solid var(--border); text-transform: uppercase; font-family: 'Space Mono', monospace; }
|
||
|
||
#net-list { flex: 1; min-height: 0; overflow-y: auto; padding: 4px 0; }
|
||
#net-list::-webkit-scrollbar { width: 4px; }
|
||
#net-list::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
|
||
.net-item { padding: 5px 12px; cursor: pointer; display: flex; align-items: center; gap: 6px; font-family: 'Space Mono', monospace; font-size: 11px; transition: background .1s; }
|
||
.net-item:hover { background: var(--surface2); }
|
||
.net-item.selected { background: rgba(0,229,160,.08); color: var(--accent); }
|
||
.net-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }
|
||
.net-item .nc { margin-left: auto; color: var(--text2); font-size: 10px; }
|
||
.net-item.wip .net-dot { background: #f0c040; }
|
||
.net-item:not(.wip) .net-dot { background: var(--accent); }
|
||
#comp-list-section { border-top: 1px solid var(--border); flex: 1; min-height: 0; display: flex; flex-direction: column; }
|
||
#comp-list { overflow-y: auto; flex: 1; min-height: 0; padding: 4px 0; }
|
||
#comp-list::-webkit-scrollbar { width: 4px; }
|
||
#comp-list::-webkit-scrollbar-thumb { background: var(--border); }
|
||
.comp-item { padding: 4px 12px; cursor: pointer; font-family: 'Space Mono', monospace; font-size: 11px; display: flex; align-items: center; gap: 6px; transition: background .1s; }
|
||
.comp-item:hover { background: var(--surface2); }
|
||
.comp-item.selected { background: rgba(0,168,255,.08); color: var(--accent2); }
|
||
.cdot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }
|
||
|
||
/* ── Canvas area ── */
|
||
#canvas-wrap { flex: 1; overflow: hidden; position: relative; background: var(--bg); }
|
||
#sch-canvas { display: block; width: 100%; height: 100%; }
|
||
#coord-readout { position: absolute; top: 8px; right: 12px; font-family: 'Space Mono', monospace; font-size: 10px; color: var(--text2); background: rgba(13,17,23,.7); padding: 3px 8px; border-radius: 4px; border: 1px solid var(--border); pointer-events: none; }
|
||
#zoom-ind { position: absolute; bottom: 60px; right: 12px; font-family: 'Space Mono', monospace; font-size: 10px; color: var(--text2); background: rgba(13,17,23,.7); padding: 3px 8px; border-radius: 4px; border: 1px solid var(--border); pointer-events: none; }
|
||
#canvas-toolbar { position: absolute; bottom: 14px; left: 50%; transform: translateX(-50%); background: var(--surface); border: 1px solid var(--border); border-radius: 8px; display: flex; gap: 4px; padding: 6px 10px; z-index: 50; }
|
||
#canvas-toolbar .btn { padding: 3px 10px; font-size: 12px; }
|
||
#hint-bar { position: absolute; top: 8px; left: 50%; transform: translateX(-50%); font-family: 'Space Mono', monospace; font-size: 10px; color: var(--text2); background: rgba(13,17,23,.85); padding: 3px 10px; border-radius: 4px; border: 1px solid var(--border); pointer-events: none; opacity: 0; transition: opacity .25s; }
|
||
#hint-bar.show { opacity: 1; }
|
||
|
||
/* ── Right panel (properties) ── */
|
||
#right-panel { width: 220px; background: var(--surface); border-left: 1px solid var(--border); display: flex; flex-direction: column; flex-shrink: 0; }
|
||
#prop-panel { padding: 10px 12px; flex: 1; overflow-y: auto; font-family: 'Space Mono', monospace; font-size: 11px; }
|
||
#prop-panel::-webkit-scrollbar { width: 4px; }
|
||
#prop-panel::-webkit-scrollbar-thumb { background: var(--border); }
|
||
.prop-row { margin-bottom: 8px; }
|
||
.prop-label { color: var(--text2); margin-bottom: 2px; font-size: 10px; text-transform: uppercase; letter-spacing: 1px; }
|
||
.prop-val { color: var(--text); }
|
||
.prop-input { width: 100%; background: var(--bg); border: 1px solid var(--border); color: var(--text); padding: 4px 6px; border-radius: 3px; font-family: 'Space Mono', monospace; font-size: 11px; margin-top: 2px; }
|
||
.prop-input:focus { outline: none; border-color: var(--accent2); }
|
||
|
||
/* ── Modals ── */
|
||
.modal-overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,.6); z-index: 1000; align-items: center; justify-content: center; }
|
||
.modal-overlay.active { display: flex; }
|
||
.modal { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 20px; width: 480px; max-width: 95vw; max-height: 80vh; display: flex; flex-direction: column; gap: 14px; }
|
||
.modal h2 { font-family: 'Share Tech Mono', monospace; color: var(--accent); font-size: 16px; letter-spacing: 2px; border-bottom: 1px solid var(--border); padding-bottom: 10px; }
|
||
.modal textarea { background: var(--bg); border: 1px solid var(--border); color: var(--text); padding: 8px; border-radius: 4px; font-family: 'Space Mono', monospace; font-size: 11px; resize: vertical; min-height: 160px; flex: 1; }
|
||
.modal textarea:focus { outline: none; border-color: var(--accent2); }
|
||
.modal-actions { display: flex; gap: 8px; justify-content: flex-end; }
|
||
.modal p { font-size: 12px; color: var(--text2); line-height: 1.6; }
|
||
.modal .fmt { background: var(--bg); border: 1px solid var(--border); border-radius: 4px; padding: 8px; font-family: 'Space Mono', monospace; font-size: 10px; color: var(--accent2); white-space: pre; overflow: auto; }
|
||
#export-content { background: var(--bg); border: 1px solid var(--border); border-radius: 4px; padding: 10px; font-family: 'Space Mono', monospace; font-size: 10px; color: var(--accent); white-space: pre; overflow: auto; max-height: 300px; flex: 1; }
|
||
#db-list { max-height: 200px; overflow-y: auto; border: 1px solid var(--border); border-radius: 4px; }
|
||
.db-item { padding: 8px 12px; cursor: pointer; font-family: 'Space Mono', monospace; font-size: 11px; border-bottom: 1px solid var(--border); transition: background .1s; display: flex; justify-content: space-between; align-items: center; }
|
||
.db-item:last-child { border-bottom: none; }
|
||
.db-item:hover { background: var(--surface2); color: var(--accent); }
|
||
.db-item.selected { background: rgba(0,229,160,.08); color: var(--accent); }
|
||
.db-item .dm { color: var(--text2); font-size: 10px; }
|
||
|
||
/* ── Toast ── */
|
||
#toast { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%) translateY(60px); background: var(--surface); border: 1px solid var(--accent); color: var(--accent); font-family: 'Space Mono', monospace; font-size: 12px; padding: 8px 20px; border-radius: 6px; z-index: 2000; transition: transform .3s; pointer-events: none; }
|
||
#toast.show { border-color: var(--accent); color: var(--accent); transform: translateX(-50%) translateY(0); }
|
||
#toast.err { border-color: var(--accent3); color: var(--accent3); transform: translateX(-50%) translateY(0); }
|
||
#toast.warn { border-color: var(--warn); color: var(--warn); transform: translateX(-50%) translateY(0); }
|
||
|
||
/* ── Context menu ── */
|
||
#ctx-menu { user-select: none; }
|
||
#ctx-menu div + div { border-top: 1px solid var(--border); }
|
||
|
||
/* ── Empty state ── */
|
||
#empty-state { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 16px; pointer-events: none; }
|
||
#empty-state .et { font-family: 'Share Tech Mono', monospace; font-size: 24px; color: var(--border); letter-spacing: 4px; }
|
||
#empty-state .es { font-family: 'Space Mono', monospace; font-size: 12px; color: var(--text2); text-align: center; line-height: 1.8; max-width: 360px; }
|
||
#empty-state .eh { font-family: 'Rajdhani', sans-serif; font-size: 14px; color: var(--accent); pointer-events: all; cursor: pointer; border: 1px solid var(--accent); padding: 6px 20px; border-radius: 4px; transition: all .15s; }
|
||
#empty-state .eh:hover { background: var(--accent); color: var(--bg); }
|
||
|
||
.responsive-text::after { content: attr(data-long); white-space: nowrap; }
|
||
/* --- MOBILE LAYOUT --- */
|
||
.mobile-only { display: none !important; }
|
||
@media (max-width: 800px) {
|
||
.mobile-only { display: inline-flex !important; }
|
||
.hide-mobile { display: none !important; }
|
||
.responsive-text::after { content: attr(data-short); }
|
||
#header .btn { padding: 4px 8px; }
|
||
#header .sep { display: none; }
|
||
#header { overflow-x: auto; padding-bottom: 2px; }
|
||
#header::-webkit-scrollbar { height: 4px; }
|
||
#header::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
|
||
|
||
#main { position: relative; }
|
||
#left-panel {
|
||
position: absolute; left: -240px; top: 0; bottom: 0; z-index: 100;
|
||
transition: left 0.25s ease; box-shadow: 4px 0 15px rgba(0,0,0,0.6);
|
||
}
|
||
#left-panel.open { left: 0; }
|
||
|
||
#right-panel {
|
||
position: absolute; right: -240px; top: 0; bottom: 0; z-index: 100;
|
||
transition: right 0.25s ease; box-shadow: -4px 0 15px rgba(0,0,0,0.6);
|
||
}
|
||
#right-panel.open { right: 0; }
|
||
|
||
#canvas-toolbar { width: 90%; max-width: 380px; justify-content: center; flex-wrap: wrap; bottom: 10px; }
|
||
}
|
||
/* ── Copyright footer (standalone mode only) ── */
|
||
#copyright-footer {
|
||
display: none;
|
||
flex: 0 0 auto;
|
||
align-self: flex-start;
|
||
width: 100%;
|
||
text-align: center;
|
||
padding: 4px 14px;
|
||
font-family: 'Space Mono', monospace;
|
||
font-size: 10px;
|
||
color: var(--text2);
|
||
background: var(--surface);
|
||
border-top: 1px solid var(--border);
|
||
z-index: 20;
|
||
}
|
||
#copyright-footer a { color: var(--text2); text-decoration: none; }
|
||
#copyright-footer a:hover { color: var(--accent); }
|
||
/* Standalone detection: show footer whenever btn-load-db is not hidden.
|
||
Works regardless of what mechanism hides the button (url param, postMessage, etc.) */
|
||
body:not(:has(#btn-load-db[style*="display: none"])) #copyright-footer { display: block; }
|
||
|
||
@media (max-width: 800px) and (max-height: 500px) {
|
||
#copyright-footer {
|
||
position: fixed;
|
||
bottom: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
z-index: 200;
|
||
}
|
||
/* Hide labels and separators in the left panel to save height */
|
||
.panel-title { display: none; }
|
||
#comp-list-section { border-top: none; }
|
||
|
||
/* Change overall app layout to row so header goes to the left edge */
|
||
body { flex-direction: row; }
|
||
|
||
/* Transform top header into a vertical sidebar */
|
||
#header {
|
||
flex-direction: column; overflow-y: auto; overflow-x: hidden;
|
||
width: auto; padding: 8px 4px;
|
||
border-bottom: none; border-right: 1px solid var(--border);
|
||
}
|
||
#header .btn-group { flex-direction: column; }
|
||
#status-msg {
|
||
display: block; text-align: center; line-height: 1.6; padding-bottom: 4px;
|
||
max-width: 40px;
|
||
white-space: normal;
|
||
margin: 0 auto;
|
||
}
|
||
#status-msg.responsive-text::after { white-space: normal; }
|
||
|
||
/* Move canvas toolbar to the RIGHT, hide zoom buttons to save height */
|
||
#canvas-toolbar {
|
||
width: auto; height: auto; flex-direction: column; flex-wrap: nowrap;
|
||
top: 50%; bottom: auto; left: auto; right: 10px; transform: translateY(-50%);
|
||
background: transparent; border: none; padding: 0; gap: 6px;
|
||
}
|
||
#canvas-toolbar .sep { display: none; }
|
||
#canvas-toolbar .btn { box-shadow: 0 2px 8px rgba(0,0,0,0.6); }
|
||
|
||
#btn-zoom-in, #btn-zoom-out { display: none; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<div id="header">
|
||
<div class="logo hide-mobile">SCHEMA<span>RETRACE</span></div>
|
||
<div class="sep hide-mobile"></div>
|
||
<div class="btn-group">
|
||
<button class="btn accent" id="btn-load-db" title="Switch to PCB ReTrace">⚡<span class="hide-mobile"> Switch to PCB ReTrace</span></button>
|
||
<button class="btn" id="btn-import-file" title="Import">📂<span class="hide-mobile"> Import</span></button>
|
||
<button class="btn" id="btn-lib-mgr" title="Libraries">📚<span class="hide-mobile"> Libraries</span></button>
|
||
</div>
|
||
<div class="sep"></div>
|
||
<div class="btn-group">
|
||
<button class="btn" id="btn-auto-place" title="Auto Layout">🔀<span class="hide-mobile"> Auto Layout</span></button>
|
||
<button class="btn" id="btn-zoom-fit" title="Fit View">⛶<span class="hide-mobile"> Fit</span></button>
|
||
</div>
|
||
<div class="sep"></div>
|
||
<div class="btn-group">
|
||
<label class="hide-mobile" style="font-size:12px;color:var(--text2);white-space:nowrap">Export:</label>
|
||
<button class="btn warn" id="btn-export-kicad" title="Export KiCad">
|
||
<img src="kicad.svg" alt="KiCad" style="width:14px; height:14px; border-radius:2px;">
|
||
<span class="hide-mobile" style="margin-left:5px;">KiCad .kicad_sch</span>
|
||
</button>
|
||
<!--<button class="btn warn" id="btn-export-spice">SPICE .cir</button>-->
|
||
</div>
|
||
<div class="spacer"></div>
|
||
<span id="status-msg">No netlist loaded</span>
|
||
</div>
|
||
|
||
<div id="main">
|
||
<div id="left-panel">
|
||
<div class="panel-title">Nets</div>
|
||
<div id="net-list"></div>
|
||
<div id="comp-list-section">
|
||
<div class="panel-title">Components</div>
|
||
<div id="comp-list"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="canvas-wrap">
|
||
<canvas id="sch-canvas"></canvas>
|
||
<div id="coord-readout">X: 0 Y: 0</div>
|
||
<div id="zoom-ind">100%</div>
|
||
<div id="hint-bar"></div>
|
||
<div id="canvas-toolbar">
|
||
<button class="btn mobile-only" id="btn-toggle-left">☰</button>
|
||
<button class="btn" id="btn-zoom-in">+</button>
|
||
<button class="btn" id="btn-zoom-out">−</button>
|
||
<button class="btn" id="btn-zoom-fit2">⛶</button>
|
||
<div class="sep hide-mobile" style="height:20px"></div>
|
||
<button class="btn" id="btn-undo">↩</button>
|
||
<button class="btn mobile-only" id="btn-toggle-right">🛈</button>
|
||
</div>
|
||
<div id="empty-state">
|
||
<div class="et">NO NETLIST</div>
|
||
<div class="es">Import a KiCad or PCB ReTrace netlist file<br>or open from PCB ReTrace Studio</div>
|
||
<div class="eh" id="es-load-btn">📂 Import Netlist</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="right-panel">
|
||
<div class="panel-title">Properties</div>
|
||
<div id="prop-panel">
|
||
<div style="color:var(--text2);font-size:10px;text-align:center;margin-top:20px">
|
||
Select a component<br>or wire segment
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="toast"></div>
|
||
|
||
<footer id="copyright-footer">
|
||
Copyright © 2026 Taras Greben — <a href="https://pcb.etaras.com" target="_blank">pcb.eTaras.com</a>
|
||
</footer>
|
||
|
||
<!-- Import modal -->
|
||
<div class="modal-overlay" id="modal-import">
|
||
<div class="modal">
|
||
<h2>📂 IMPORT NETLIST</h2>
|
||
<p>Paste or open a file. Accepted: PCB ReTrace JSON, KiCad XML netlist, KiCad S-expr .net.</p>
|
||
<div class="fmt">{ "nets": [ { "name": "NET1",
|
||
"nodes": [{"ref":"R1","pin":"1"},{"ref":"C1","pin":"1"}] } ],
|
||
"components": [ {"ref":"R1","value":"10k","type":"R"} ] }</div>
|
||
<textarea id="import-textarea" placeholder="Paste netlist here…"></textarea>
|
||
<div style="display:flex;gap:8px;align-items:center">
|
||
<button class="btn" id="btn-pick-file">📁 Open File</button>
|
||
<input type="file" id="file-input" accept=".json,.net,.cir,.txt" style="display:none">
|
||
<span id="file-name" style="font-family:'Space Mono',monospace;font-size:10px;color:var(--text2)">No file</span>
|
||
</div>
|
||
<div class="modal-actions">
|
||
<button class="btn" id="btn-import-cancel">Cancel</button>
|
||
<button class="btn accent" id="btn-import-ok">Import</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Replace Symbol modal -->
|
||
<div class="modal-overlay" id="modal-replace-symbol">
|
||
<div class="modal" style="width: 500px;">
|
||
<h2>✨ REPLACE SYMBOL</h2>
|
||
<div style="display:flex; flex-direction:column; gap: 4px; margin-bottom: 8px;">
|
||
<input type="text" id="sym-search" placeholder="Search symbols..." class="prop-input" style="width:100%;">
|
||
<div style="font-size:9px; color:var(--text2);">Advanced filters: <code>name:dio</code> <code>lib:device</code> <code>ref:D</code> <code>desc:zener</code></div>
|
||
</div>
|
||
<div id="sym-list" style="max-height: 300px; overflow-y: auto; border: 1px solid var(--border); border-radius: 4px;">
|
||
<!-- list populated via JS -->
|
||
</div>
|
||
<div class="modal-actions" style="margin-top: 14px;">
|
||
<button class="btn" id="btn-replace-cancel">Cancel</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Library Manager Modal -->
|
||
|
||
<div class="modal-overlay" id="modal-library-manager">
|
||
<div class="modal" style="width: 500px;">
|
||
<h2>📚 KICAD LIBRARY MANAGER</h2>
|
||
|
||
<div id="lib-default-ui" style="display:flex; flex-direction:column; gap:8px;">
|
||
<p>Import KiCad symbols from a local file (.zip, .kicad_sym) or a remote URL.</p>
|
||
<label style="font-size:10px; color:var(--text2); text-transform:uppercase;">Remote URL</label>
|
||
<div style="display:flex; gap:8px;">
|
||
<input type="text" id="lib-remote-url" value="https://pcb.etaras.com/assets/kicad-symbols-master.zip" placeholder="https://.../library.zip" class="prop-input" style="flex:1;">
|
||
<button class="btn accent" id="btn-lib-import-url">⬇ Fetch</button>
|
||
</div>
|
||
|
||
<div style="text-align:center; color:var(--text2); margin: 4px 0;">— OR —</div>
|
||
|
||
<label style="font-size:10px; color:var(--text2); text-transform:uppercase;">Local File</label>
|
||
<div style="display:flex; gap:8px; align-items:center;">
|
||
<button class="btn" id="btn-lib-pick-file">📁 Select File</button>
|
||
<input type="file" id="lib-file-input" accept=".zip,.kicad_sym" style="display:none">
|
||
<span id="lib-file-name" style="font-family:'Space Mono',monospace;font-size:10px;color:var(--text2)">No file</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="lib-zip-explorer" style="display:none; flex-direction:column; gap:8px;">
|
||
<div style="font-size:11px; color:var(--text2);">Select libraries or symbols to import:</div>
|
||
<div style="display:flex; gap:6px;">
|
||
<button class="btn" id="btn-zip-sel-all">Select All</button>
|
||
<button class="btn" id="btn-zip-sel-none">Select None</button>
|
||
</div>
|
||
<div id="zip-tree" style="max-height: 240px; overflow-y: auto; border: 1px solid var(--border); padding: 6px; background: var(--bg); border-radius:4px; font-family:'Space Mono', monospace; font-size:11px;"></div>
|
||
<div style="display:flex; gap: 8px; margin-top:4px;">
|
||
<button class="btn accent" id="btn-zip-import">Import Selected</button>
|
||
<button class="btn" id="btn-zip-cancel">Cancel</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="lib-import-log" style="margin-top:12px; max-height:100px; overflow-y:auto; font-size:10px; font-family:'Space Mono',monospace; color:var(--accent2); background:var(--bg); padding:6px; border:1px solid var(--border); border-radius:4px;"></div>
|
||
|
||
<div class="modal-actions" style="margin-top: 14px;">
|
||
<button class="btn" id="btn-lib-close">Close</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Export modal -->
|
||
<div class="modal-overlay" id="modal-export">
|
||
<div class="modal" style="width:560px">
|
||
<h2 id="export-title">EXPORT</h2>
|
||
<pre id="export-content"></pre>
|
||
<div class="modal-actions">
|
||
<button class="btn" id="btn-export-close">Close</button>
|
||
<button class="btn accent" id="btn-export-copy">📋 Copy</button>
|
||
<button class="btn warn" id="btn-export-download">⬇ Download</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script type="module" src="schema/app.js"></script>
|
||
</body>
|
||
</html>
|