e4a80dc0e6
- Roadmap #22: File→Import JSON to restore a diagram (drag-drop or file picker) - Roadmap #23: Duplicate diagram from diagram list - Roadmap #24: DRC panel with collapsible categories (unconnected pins, dup refs, no length, no P/N) - Multi-view tabs: named views per diagram with independent device/wire layouts; snapshot-on-entry prevents bleed - Keyboard arrow nudge for selected device(s) - Undo for move, duplicate, label/property changes - Wire jump arcs toggle (⌒ Jumps button in toolbar) - Shift+click multi-wire selection; right-click bulk clear waypoints or auto-route - Auto-route: obstacle-avoiding ortho routing for single or bulk selected wires - Delete view tab with × button; right-click context menu for rename/delete Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
430 lines
20 KiB
HTML
430 lines
20 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-jumps" title="Toggle wire jump arcs at crossings" class="jumps-btn active">⌒ Jumps</button>
|
|
<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">
|
|
<button id="btn-drc" title="Run design-rule check">⚠ DRC</button>
|
|
<div id="saved-indicator">✓ Saved</div>
|
|
<div class="export-wrap">
|
|
<button id="export-toggle">Export ▾</button>
|
|
<div id="export-menu">
|
|
<button id="btn-import">📂 Import JSON</button>
|
|
<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>
|
|
<input id="import-file-input" type="file" accept=".json" style="display:none">
|
|
</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">
|
|
<!-- View tab bar -->
|
|
<div id="view-tab-bar" style="display:none">
|
|
<button class="view-tab active" data-view-id="main">Main</button>
|
|
<button id="btn-add-view" title="Add view">+</button>
|
|
</div>
|
|
<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>
|
|
|
|
<!-- ── DRC Modal ──────────────────────────────────────────────────────────────── -->
|
|
<div id="drc-modal" style="display:none">
|
|
<div class="modal-box" style="min-width:480px;max-width:640px">
|
|
<div class="modal-header">
|
|
<h3>Design Rule Check</h3>
|
|
</div>
|
|
<div class="modal-body" id="drc-results" style="max-height:400px;overflow-y:auto;font-size:12px;padding:10px;gap:6px">
|
|
</div>
|
|
<div class="modal-footer">
|
|
<div style="flex:1"></div>
|
|
<button id="drc-close" class="btn-primary">Close</button>
|
|
</div>
|
|
</div>
|
|
</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>
|