Files
Wiring-Designer/frontend/index.html
T
Kyle f32203b630 Initial commit — wiring diagram maker with full feature set
- FastAPI + SQLite backend with routers for diagrams, devices, wires,
  bundles, connectors, export, and Octopart/Nexar search
- Konva.js canvas: ortho/direct/curved routing, wire crossings, harness
  mode, snap-to-grid, multi-select, drag, resize, undo
- Wire features: color/stripe, twisted pair helix, shielded glow,
  gauge size label, multi-core bundle grouping, length/unit tracking
- Device library: connector, terminal block, cable, splice, label, group,
  relay, fuse, switch, component with custom connector support
- Exports: BOM CSV, assembly TXT, JSON, formboard layout, PNG image
- Auto-migration for schema changes via ALTER TABLE at startup

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 12:51:21 -04:00

406 lines
19 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WireDraw — Wiring Diagram Maker</title>
<link rel="stylesheet" href="/css/main.css">
</head>
<body>
<div id="app">
<!-- ── Toolbar ──────────────────────────────────────────────────────────── -->
<header id="toolbar">
<div class="toolbar-left">
<span class="app-title">⚡ WireDraw</span>
<input id="diagram-name" type="text" class="diagram-name-input" placeholder="Diagram name…">
</div>
<div class="toolbar-center">
<!-- Mode -->
<button id="btn-select" class="mode-btn active" title="Select mode (S)">▸ Select</button>
<button id="btn-wire" class="mode-btn" title="Draw wire: drag from pin to pin (W)">∿ Wire</button>
<div class="sep"></div>
<!-- Routing -->
<span class="toolbar-label">Route:</span>
<button id="btn-route-ortho" class="route-btn active" title="Orthogonal (right-angle) routing">⌐ Ortho</button>
<button id="btn-route-direct" class="route-btn" title="Direct (straight line) routing">⤢ Direct</button>
<button id="btn-route-curved" class="route-btn" title="Curved (smooth bezier) routing">∿ Curved</button>
<div class="sep"></div>
<!-- Actions -->
<button id="btn-snap" title="Toggle grid snap (G)" class="snap-btn">⊹ Snap</button>
<button id="btn-harness" title="Toggle harness view (H)">⌇ Harness</button>
<button id="btn-fit" title="Fit canvas to devices (F)">⊞ Fit</button>
<button id="btn-duplicate" title="Duplicate selected (Ctrl+D)">⊕ Dupe</button>
<button id="btn-undo" title="Undo (Ctrl+Z)">↩ Undo</button>
<button id="btn-delete" class="danger-btn" title="Delete selected (Del)">✕ Delete</button>
</div>
<div class="toolbar-right">
<div id="saved-indicator">✓ Saved</div>
<div class="export-wrap">
<button id="export-toggle">Export ▾</button>
<div id="export-menu">
<button id="btn-bom">📋 BOM (CSV)</button>
<button id="btn-assembly">📄 Assembly (TXT)</button>
<button id="btn-json">{ } JSON</button>
<button id="btn-img">🖼 Image (PNG)</button>
<button id="btn-formboard">📐 Formboard (1:1)</button>
</div>
</div>
</div>
</header>
<!-- ── Mode bar ─────────────────────────────────────────────────────────── -->
<div id="mode-indicator"></div>
<!-- ── Main ─────────────────────────────────────────────────────────────── -->
<div id="main">
<!-- Left sidebar -->
<aside id="left-sidebar">
<!-- Diagram list -->
<div class="sidebar-section" style="flex-shrink:0">
<div class="section-header">
Diagrams
<button id="btn-new" title="New diagram" style="padding:2px 8px;font-size:16px;line-height:1">+</button>
</div>
<div id="diagram-list" style="overflow-y:auto;max-height:180px"></div>
</div>
<!-- Tabs: Devices / Connectors -->
<div class="tab-bar">
<button class="sidebar-tab active" data-tab="devices">Devices</button>
<button class="sidebar-tab" data-tab="connectors">Connector Library</button>
</div>
<!-- Devices panel -->
<div class="tab-panel" data-panel="devices" style="overflow-y:auto;flex:1">
<p class="muted small" style="padding:5px 10px">Drag to canvas or double-click to add</p>
<div id="device-library"></div>
</div>
<!-- Connector library panel -->
<div class="tab-panel" data-panel="connectors" style="display:none;flex-direction:column;overflow:hidden;flex:1">
<div style="padding:6px 8px;display:flex;flex-direction:column;gap:4px;flex-shrink:0">
<div style="display:flex;gap:4px;align-items:center">
<input id="lib-search" type="text" placeholder="Search connectors…" style="flex:1;background:#0e0e1e;border:1px solid #2a2a44;color:#c0c4e8;padding:5px 8px;border-radius:4px;font-size:12px">
<button id="btn-new-connector" title="Create custom connector" style="padding:4px 8px;font-size:13px;flex-shrink:0">+</button>
</div>
<select id="lib-category" style="background:#0e0e1e;border:1px solid #2a2a44;color:#c0c4e8;padding:4px 6px;border-radius:4px;font-size:11px"></select>
</div>
<p class="muted small" style="padding:0 10px 4px">Drag to canvas or double-click to add</p>
<div id="lib-list" style="overflow-y:auto;flex:1;padding:4px"></div>
</div>
</aside>
<!-- Canvas -->
<main id="canvas-area">
<div id="canvas-container"></div>
<div id="canvas-placeholder">
<div class="placeholder-content">
<h2>⚡ WireDraw</h2>
<p>Create or open a diagram to begin</p>
<button onclick="app.newDiagram()">+ New Diagram</button>
</div>
</div>
</main>
<!-- Right sidebar — Properties -->
<aside id="right-sidebar">
<div class="section-header">Properties</div>
<!-- Empty state -->
<div id="props-empty" style="padding:20px 12px;color:#444466;font-size:12px;text-align:center">
Select a device or wire to edit its properties.
</div>
<!-- Multi-select state -->
<div id="props-multi" style="display:none;padding:24px 12px;text-align:center">
<div id="props-multi-count" style="font-size:13px;color:#c8ccee"></div>
<div style="margin-top:6px;font-size:11px;color:#444466">Drag any to move all · Ctrl+D to duplicate · Del to delete</div>
</div>
<!-- Device properties -->
<div id="props-device" style="display:none">
<div class="prop-row">
<label>Type</label>
<span id="prop-type" class="prop-value"></span>
</div>
<div class="prop-row">
<label>Reference</label>
<input id="prop-reference" type="text" placeholder="J1, TB1, U1…">
</div>
<div class="prop-row">
<label>Label</label>
<input id="prop-label" type="text" placeholder="Friendly name">
</div>
<div class="prop-row">
<label>Part Number</label>
<div style="display:flex;gap:4px">
<input id="prop-partnumber" type="text" placeholder="Mfr part number" style="flex:1;min-width:0">
<button id="btn-octopart-search" title="Search Octopart / Nexar" style="padding:4px 7px;font-size:13px;flex-shrink:0">🔍</button>
</div>
<div id="octopart-results" style="display:none"></div>
<div id="octopart-status-msg" style="display:none;font-size:10px;color:#666688;margin-top:3px"></div>
</div>
<div class="prop-row">
<label>Manufacturer</label>
<input id="prop-manufacturer" type="text" placeholder="Manufacturer">
</div>
<div class="prop-row" id="prop-datasheet-row" style="display:none">
<label>Datasheet</label>
<a id="prop-datasheet-link" href="#" target="_blank" rel="noopener noreferrer"
style="font-size:11px;color:#7788ff;word-break:break-all;display:block">📄 Open datasheet</a>
</div>
<div class="prop-row">
<label>Font Size</label>
<input id="prop-fontsize" type="number" min="6" max="72" step="1" placeholder="12" style="width:64px">
</div>
<!-- Group-specific section (shown only for section box) -->
<div id="group-section" style="display:none">
<div class="prop-row">
<label>Fill Color</label>
<input id="group-fill-color" type="color" value="#2828a0" class="color-swatch">
</div>
<div class="prop-row">
<label>Opacity</label>
<div style="display:flex;gap:6px;align-items:center">
<input id="group-fill-opacity" type="range" min="0.02" max="0.6" step="0.01" value="0.15" style="flex:1">
<span id="group-fill-opacity-val" style="font-size:10px;color:#778;width:28px;text-align:right">15%</span>
</div>
</div>
</div>
<!-- Cable-specific section (shown only for cable devices) -->
<div id="cable-section" style="display:none">
<div class="prop-row">
<label>Jacket Color</label>
<input id="cable-jacket-color" type="color" value="#2a2a2a" class="color-swatch">
</div>
<div class="prop-row">
<label>Sleeve Length</label>
<div style="display:flex;gap:6px;align-items:center">
<input id="cable-sleeve-length" type="range" min="0" max="200" step="5" value="60" style="flex:1">
<span id="cable-sleeve-length-val" style="font-size:10px;color:#778;width:32px;text-align:right">60 px</span>
</div>
</div>
<div class="prop-row">
<label>Conductors</label>
<table class="pin-table">
<thead><tr><th>#</th><th>Name</th><th>Color</th><th></th></tr></thead>
<tbody id="conductor-table-body"></tbody>
</table>
<button id="prop-add-conductor" style="margin-top:5px;width:100%;font-size:11px;padding:3px 6px">+ Add Conductor</button>
</div>
</div>
<div class="prop-row">
<label>Pins <span class="muted small">(rename inline)</span></label>
<table class="pin-table">
<thead><tr><th>ID</th><th>Name</th><th title="L=left R=right T=top B=bottom">Side</th><th></th></tr></thead>
<tbody id="pin-table-body"></tbody>
</table>
<div style="margin-top:5px;display:flex;gap:4px">
<button id="prop-add-pin" style="flex:1;font-size:11px;padding:3px 6px">+ Add Pin</button>
<button id="prop-save-connector" class="btn-primary" style="flex:1;font-size:11px;padding:3px 6px">⬆ Save to Library</button>
</div>
</div>
</div>
<!-- Wire properties -->
<div id="props-wire" style="display:none">
<div class="prop-row">
<label>From</label>
<span id="wire-from" class="prop-value mono"></span>
</div>
<div class="prop-row">
<label>To</label>
<span id="wire-to" class="prop-value mono"></span>
</div>
<div class="prop-row">
<label>Label</label>
<input id="wire-label" type="text" placeholder="Wire label / ID">
</div>
<div class="prop-row">
<label>Primary Color</label>
<div style="display:flex;gap:6px;align-items:center">
<input id="wire-color" type="color" value="#CC0000" class="color-swatch">
<select id="wire-color-preset" style="flex:1;background:#0e0e1e;border:1px solid #2a2a44;color:#c0c4e8;padding:4px 6px;border-radius:4px;font-size:11px">
<option value="">— Preset colors —</option>
</select>
</div>
</div>
<div class="prop-row">
<label id="wire-stripe-lbl">Stripe Color</label>
<div style="display:flex;gap:6px;align-items:center">
<label id="wire-stripe-toggle-lbl" style="display:flex;align-items:center;gap:4px;cursor:pointer;font-size:11px;color:#778">
<input id="wire-stripe-enabled" type="checkbox"> stripe
</label>
<input id="wire-stripe" type="color" value="#ffffff" disabled class="color-swatch" style="opacity:0.3">
</div>
</div>
<div class="prop-row">
<label>Wire Gauge</label>
<select id="wire-gauge">
<optgroup label="AWG">
<option>4 AWG</option>
<option>6 AWG</option>
<option>8 AWG</option>
<option>10 AWG</option>
<option>12 AWG</option>
<option>14 AWG</option>
<option>16 AWG</option>
<option selected>18 AWG</option>
<option>20 AWG</option>
<option>22 AWG</option>
<option>24 AWG</option>
<option>26 AWG</option>
</optgroup>
<optgroup label="Metric (mm²)">
<option>0.5 mm²</option>
<option>0.75 mm²</option>
<option>1.0 mm²</option>
<option>1.5 mm²</option>
<option>2.0 mm²</option>
<option>2.5 mm²</option>
<option>4.0 mm²</option>
<option>6.0 mm²</option>
<option>10 mm²</option>
<option>16 mm²</option>
<option>25 mm²</option>
<option>35 mm²</option>
<option>50 mm²</option>
<option>70 mm²</option>
<option>95 mm²</option>
</optgroup>
</select>
</div>
<div class="prop-row" style="display:flex;gap:6px">
<div style="flex:1">
<label>Length</label>
<input id="wire-length" type="number" min="0" step="0.5" placeholder="0.0">
</div>
<div style="width:64px">
<label>Unit</label>
<select id="wire-unit">
<option>in</option><option>cm</option><option>mm</option><option>ft</option>
</select>
</div>
</div>
<div class="prop-row">
<label>Twisted Pair</label>
<div style="display:flex;gap:8px;align-items:center">
<label style="display:flex;align-items:center;gap:4px;cursor:pointer;font-size:11px;color:#778">
<input id="wire-twisted" type="checkbox"> twisted pair
</label>
<input id="wire-twist-pitch" type="number" min="8" max="64" step="2" value="16" disabled
style="width:48px;opacity:0.3">
<span id="wire-twist-pitch-label" style="font-size:10px;color:#444466">px pitch</span>
</div>
</div>
<div class="prop-row">
<label>Shielded</label>
<label style="display:flex;align-items:center;gap:4px;cursor:pointer;font-size:11px;color:#778">
<input id="wire-shielded" type="checkbox"> shielded cable
</label>
</div>
<div class="prop-row">
<label>Size Label</label>
<label style="display:flex;align-items:center;gap:4px;cursor:pointer;font-size:11px;color:#778">
<input id="wire-show-size-label" type="checkbox"> show gauge on canvas
</label>
</div>
<div class="prop-row">
<label>Notes</label>
<textarea id="wire-notes" rows="3" placeholder="Notes…" style="resize:vertical"></textarea>
</div>
<div class="prop-section-header" style="margin-top:10px;margin-bottom:4px;font-size:10px;color:#556;text-transform:uppercase;letter-spacing:.05em">Multi-Core Bundle</div>
<div class="prop-row">
<label>Bundle</label>
<div style="display:flex;gap:4px;flex:1">
<select id="wire-bundle-select" style="flex:1">
<option value="">— none —</option>
</select>
<button id="wire-bundle-new" title="New bundle" style="padding:2px 7px;font-size:13px">+</button>
</div>
</div>
<div id="wire-bundle-edit" style="display:none">
<div class="prop-row">
<label>Label</label>
<input id="wire-bundle-label" type="text" placeholder="e.g. CAN Bus" style="flex:1">
</div>
<div class="prop-row">
<label>Jacket</label>
<input id="wire-bundle-color" type="color" value="#2a2a2a" style="width:50px">
<button id="wire-bundle-delete" style="margin-left:auto;padding:2px 7px;font-size:11px;color:#c66;background:none;border:1px solid #c66;border-radius:3px;cursor:pointer">Delete</button>
</div>
</div>
</div>
</aside>
</div><!-- #main -->
</div><!-- #app -->
<!-- ── Custom Connector Modal ──────────────────────────────────────────────── -->
<div id="connector-modal" style="display:none">
<div class="modal-box">
<div class="modal-header">
<h3 id="cm-modal-title">New Custom Connector</h3>
</div>
<div class="modal-body">
<div class="cm-row">
<label class="cm-label" for="cm-name">Name <span style="color:#ff6666">*</span></label>
<input id="cm-name" type="text" class="cm-input" placeholder="e.g. AMP Superseal 6P">
</div>
<div class="cm-row cm-row-2col">
<div>
<label class="cm-label" for="cm-category">Category</label>
<input id="cm-category" type="text" class="cm-input" placeholder="Custom">
</div>
<div>
<label class="cm-label" for="cm-pincount">Pin Count <span style="color:#ff6666">*</span></label>
<input id="cm-pincount" type="number" class="cm-input" min="1" max="64" value="4">
</div>
</div>
<div class="cm-row cm-row-2col">
<div>
<label class="cm-label" for="cm-manufacturer">Manufacturer</label>
<input id="cm-manufacturer" type="text" class="cm-input" placeholder="e.g. TE Connectivity">
</div>
<div>
<label class="cm-label" for="cm-partnumber">Part Number</label>
<input id="cm-partnumber" type="text" class="cm-input" placeholder="e.g. 1-1703630-1">
</div>
</div>
<div class="cm-row">
<label class="cm-label" for="cm-description">Description</label>
<input id="cm-description" type="text" class="cm-input" placeholder="Optional description">
</div>
<div class="cm-row">
<label class="cm-label">Pin Labels <span class="muted small">(optional)</span></label>
<div id="cm-pin-labels"></div>
</div>
</div>
<div class="modal-footer">
<button id="cm-delete" class="danger-btn" style="display:none">Delete</button>
<div style="flex:1"></div>
<button id="cm-cancel">Cancel</button>
<button id="cm-save" class="btn-primary">Save</button>
</div>
</div>
</div>
<!-- ── Context Menu ──────────────────────────────────────────────────────────── -->
<div id="ctx-menu">
<!-- populated dynamically by app.js -->
</div>
<script src="https://unpkg.com/konva@9/konva.min.js"></script>
<script src="/js/api.js"></script>
<script src="/js/deviceTypes.js"></script>
<script src="/js/connectorLibrary.js"></script>
<script src="/js/canvas.js"></script>
<script src="/js/app.js"></script>
</body>
</html>