diff options
author | jaseg <git@jaseg.de> | 2023-04-09 17:24:50 +0200 |
---|---|---|
committer | jaseg <git@jaseg.de> | 2023-04-09 17:24:50 +0200 |
commit | c9dff5450fa40f49727f5c447081d0df28484b1b (patch) | |
tree | 133eb03e79be88974ed09b0d35680ace9c037800 /gerbonara/cad/protoserve_data | |
parent | 3b5fb41ecb9828fd13fe1a849448a3d41c8f1867 (diff) | |
download | gerbonara-c9dff5450fa40f49727f5c447081d0df28484b1b.tar.gz gerbonara-c9dff5450fa40f49727f5c447081d0df28484b1b.tar.bz2 gerbonara-c9dff5450fa40f49727f5c447081d0df28484b1b.zip |
protogen web interface works
Diffstat (limited to 'gerbonara/cad/protoserve_data')
-rw-r--r-- | gerbonara/cad/protoserve_data/protoserve.html | 899 |
1 files changed, 899 insertions, 0 deletions
diff --git a/gerbonara/cad/protoserve_data/protoserve.html b/gerbonara/cad/protoserve_data/protoserve.html new file mode 100644 index 0000000..4b116b2 --- /dev/null +++ b/gerbonara/cad/protoserve_data/protoserve.html @@ -0,0 +1,899 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <title>Protoserve</title> + <link rel="icon" type="image/png" href="static/favicon-512.png"> + <link rel="apple-touch-icon" href="static/favicon-512.png"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> +<style> +:root { + --u-display-metric: default; + --u-display-us: none; +} + +html, body { + margin: 0; + width: 100%; + height: 100%; +} + +body { + display: grid; + grid-template-columns: clamp(200px, 500px, 50vw) 1fr; + grid-template-rows: 1fr 0fr; + grid-template-areas: "controls main" + "links main"; + +} + +#controls { + grid-area: controls; + user-select: none; + display: grid; + grid-template-columns: 10fr 1fr 1fr; + align-content: start; + overflow-y: scroll; +} + +label { + grid-column-start: 1; + grid-column-end: span 3; + display: grid; + grid-template-columns: subgrid; + text-align: right; +} + +input[type="checkbox"] { + justify-self: start; +} + +input { + align-self: start; +} + +.group > h4, .group > h5 { + text-align: center; + margin: 5px; + grid-column-start: 1; + grid-column-end: span 3; +} + +.expand > :first-child { + grid-column-start: 1; + grid-column-end: span 3; +} + +.group, .field, .expand { + grid-column-start: 1; + grid-column-end: span 3; + display: grid; + grid-template-columns: subgrid; + align-content: start; +} + +.group { + background-color: hsl(.0turn 50% 10% / 4%); + padding: 10px; + border-radius: 10px; + box-shadow: 0 0 8px 0px hsl(0 0% 0% / 20%); + margin: 10px 0 10px 0; +} + +.expand > .field:first-child { + display: grid; + grid-template-columns: 1fr 100fr 1fr; + align-items: center; + text-align: left; + width: 100%; + position: relative; +} + +.group > .content { + grid-column-start: 1; + grid-column-end: span 3; + display: flex; + flex-direction: column; + text-align: center; +} + +.group > div > .proportion { + display: none; +} + +.proportional > div > .proportion { + display: grid; +} + +.split-sides .double-sided-only { + display: none; +} + +.split-sides > .placeholder .area-controls { + display: none; +} + +.board > .placeholder > .area-controls { + display: none; +} + +.area-controls .area-move { + display: none; +} + +.area-controls .area-move::before { + content: "/"; + padding: 0 5px 0 5px; +} + +.group.proportional > .group > .area-controls .area-move { + display: block; +} + +.content.area-controls { + flex-direction: row; + justify-content: center; +} + +.field > input, .field > select { max-width: 5em; + text-align: right; + margin: 0 5px 0 5px; +} + +.group.expand { + border-radius: 0; +} + +.expand > :first-child { + font-weight: bold; +} + +.expand.collapsed > :nth-child(n+2) { + display: none; +} + +.unit.metric { + display: var(--u-display-metric); +} + +.unit.us { + display: var(--u-display-us); +} + +#preview { + grid-area: main; +} + +#preview-image { + width: 100%; + height: 100%; + object-fit: contain; +} + +#links { + grid-area: links; +} + +.layout-area { + grid-column-start: 1; + grid-column-end: span 3; +} + +.drop-target { + grid-column-start: 1; + grid-column-end: span 3; + text-align: center; + display: none; +} + +.group.drop-enabled > .drop-target { + display: block; +} + +.placeholder hr { + width: 3em; + border: none; + border-top: 1px solid hsl(0 0% 60%); +} + +#controls.move-in-progress input { + background-color: hsl(0 0% 85%); +} + +#controls.move-in-progress { + color: hsl(0 0% 60%); +} + +</style> + </head> + <body> + <div id="controls"> + <div class="group board"> + <h4>Board settings</h4> + <label>Units + <select name='units' value="metric"> + <option value="metric">Metric</option> + <option value="us">US Customary</option> + </select> + </label> + + <label>Board width + <input name="width" type="text" placeholder="width" value="100"> + <span class="unit metric">mm</span> + <span class="unit us">inch</span> + </label> + + <label>Board height + <input name="height" type="text" placeholder="height" value="80"> + <span class="unit metric">mm</span> + <span class="unit us">inch</span> + </label> + + <div class="group expand" data-group="round_corners"> + <label>Round corners + <input name="enabled" type="checkbox" checked> + </label> + + <label>Radius + <input name="radius" type="text" placeholder="radius" value="1.5"> + <span class="unit metric">mm</span> + <span class="unit us">inch</span> + </label> + </div> + + <div class="group expand" data-group="mounting_holes"> + <label>Mounting holes + <input name="enabled" type="checkbox" name="has_holes" checked> + </label> + + <label>Diameter + <input type="text" placeholder="diameter" name="diameter" value="3.2"></input> + <span class="unit metric">mm</span> + <span class="unit us">inch</span> + </label> + + <label>Board edge to hole center + <input type="text" placeholder="distance" name="offset" value="5"></input> + <span class="unit metric">mm</span> + <span class="unit us">inch</span> + </label> + </div> + + <h4>Content</h4> + <div class="group placeholder"></div> + </div> + </div> + <div id="preview"> + <img id="preview-image" alt="Automatically generated preview image"/> + </div> + <div id="links"> + <a href="#controls">Settings</a> + <a href="#preview">Preview</a> + <a href='/download'> + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="1em"> + <title>Download</title> + <!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M288 32c0-17.7-14.3-32-32-32s-32 14.3-32 32V274.7l-73.4-73.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l128 128c12.5 12.5 32.8 12.5 45.3 0l128-128c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L288 274.7V32zM64 352c-35.3 0-64 28.7-64 64v32c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V416c0-35.3-28.7-64-64-64H346.5l-45.3 45.3c-25 25-65.5 25-90.5 0L165.5 352H64zm368 56a24 24 0 1 1 0 48 24 24 0 1 1 0-48z"/> + </svg> + Gerbers + </a> + </div> + + <template id="tpl-drop-target"> + <a class="drop-target" href="#"> + <svg viewBox="0 0 532 532" width="1em" xmlns="http://www.w3.org/2000/svg"> + <title>Move here</title> + <path id="path2" d="m 424.025,300.075 c 17.7,0 32,-14.3 32,-32 0,-17.7 -14.3,-32 -32,-32 h -82.7 l 181.3,-181.4 c 12.5,-12.5 12.5,-32.8 0,-45.3 -12.5,-12.5 -32.8,-12.5 -45.3,0 l -181.3,181.4 v -82.7 c 0,-17.7 -14.3,-32 -32,-32 -17.7,0 -32,14.3 -32,32 v 160 c 0,17.7 14.3,32 32,32 z M 80,52 C 35.8,52 0,87.8 0,132 v 320 c 0,44.2 35.8,80 80,80 h 320 c 44.2,0 80,-35.8 80,-80 v -72 c 0,-17.7 -14.3,-32 -32,-32 -17.7,0 -32,14.3 -32,32 v 72 c 0,8.8 -7.2,16 -16,16 H 80 c -8.8,0 -16,-7.2 -16,-16 V 132 c 0,-8.8 7.2,-16 16,-16 h 72 c 17.7,0 32,-14.3 32,-32 0,-17.7 -14.3,-32 -32,-32 z" /> + </svg> + </a> + </template> + + <template id="tpl-g-layout"> + <div data-type="layout" class="group proportional"> + <h4>Proportional Layout</h4> + <span class="content area-controls">(<a href="#" class="area-remove">Remove</a><a href="#" class="area-move">Move</a>)</span> + <label class="proportion">Proportion + <input type="text" name="layout_prop" value="1"> + </label> + + <h5>Layout settings</h4> + <label>Direction + <select name="direction" value="horizontal"> + <option value="h">horizontal</option> + <option value="v">vertical</option> + </select> + </label> + + <h5>Content</h4> + <div class="drop-target"></div> + <div class="placeholder"></div> + <div class="drop-target"></div> + <div class="placeholder"></div> + <div class="drop-target"></div> + <a class="content add-element" href="#">Add element</a> + </div> + </template> + + <template id="tpl-g-twoside"> + <div class="group split-sides"> + <h4>Split front and back</h4> + <span class="content area-controls">(<a href="#" class="area-remove">Remove</a><a href="#" class="area-move">Move</a>)</span> + <label class="proportion">Proportion + <input type="text" name="layout_prop" value="1"> + </label> + + <h5>Front</h5> + <div class="placeholder"></div> + <h5>Back</h5> + <div class="placeholder"></div> + </div> + </template> + + <template id="tpl-g-placeholder"> + <div data-type="placeholder" class="group placeholder"> + <h4>Empty area</h4> + <span class="content area-controls">(<a href="#" class="area-remove">Remove</a><a href="#" class="area-move">Move</a>)</span> + <label class="proportion">Proportion + <input type="text" name="layout_prop" value="1"> + </label> + + <div class="content"> + <a href="#" data-placeholder="layout">Create Layout</a> + <a href="#" data-placeholder="twoside" class="double-sided-only">Split front and back</a> + <hr/> + <a href="#" data-placeholder="smd">SMD area</a> + <a href="#" data-placeholder="tht" class="double-sided-only">THT area</a> + <a href="#" data-placeholder="manhattan">Manhattan area</a> + <a href="#" data-placeholder="flower"class="double-sided-only">THT Flower area</a> + <a href="#" data-placeholder="powered"class="double-sided-only">Powered THT area</a> + <a href="#" data-placeholder="rf"class="double-sided-only">RF THT area</a> + </div> + </div> + </template> + + <template id="tpl-g-smd"> + <div data-type="smd" class="group smd"> + <h4>SMD area</h4> + <span class="content area-controls">(<a href="#" class="area-remove">Remove</a><a href="#" class="area-move">Move</a>)</span> + <label class="proportion">Proportion + <input type="text" name="layout_prop" value="1"> + </label> + + <h5>Area Settings</h5> + <label>Pitch X + <input type="text" name="pitch_x" placeholder="length" value="1.27"> + <span class="unit metric">mm</span> + <span class="unit us">mil</span> + </label> + <label>Pitch Y + <input type="text" name="pitch_y" placeholder="length" value="2.54"> + <span class="unit metric">mm</span> + <span class="unit us">mil</span> + </label> + <label>Clearance + <input type="text" name="clearance" placeholder="length" value="0.3"> + <span class="unit metric">mm</span> + <span class="unit us">mil</span> + </label> + <label>Pad shape + <select name="pad_shape" value="rect"> + <option value="rect">(Rounded) Rectangle</option> + <option value="circle">Circle</option> + <option value="obround">Obround</option> + </select> + </label> + <label class="only-shape rect">Corner radius + <input type="text" name="pad_h" placeholder="length" value="0"> + <span class="unit metric">mm</span> + <span class="unit us">mil</span> + </label> + </div> + </template> + + <template id="tpl-g-tht"> + <div data-type="tht" class="group tht"> + <h4>THT area</h4> + <span class="content area-controls">(<a href="#" class="area-remove">Remove</a><a href="#" class="area-move">Move</a>)</span> + <label class="proportion">Proportion + <input type="text" name="layout_prop" value="1"> + </label> + + <h5>Area Settings</h5> + <label>Pitch X + <input type="text" name="pitch_x" placeholder="length" value="2.54"> + <span class="unit metric">mm</span> + <span class="unit us">mil</span> + </label> + <label>Pitch Y + <input type="text" name="pitch_y" placeholder="length" value="2.54"> + <span class="unit metric">mm</span> + <span class="unit us">mil</span> + </label> + <label>Clearance + <input type="text" name="clearance" placeholder="length" value="0.5"> + <span class="unit metric">mm</span> + <span class="unit us">mil</span> + </label> + <label>Plating + <select name="plating" value="through"> + <option value="plated">Double-sided, through-plated</option> + <option value="nonplated">Double-sided, non-plated</option> + <option value="singleside">Single-sided, non-plated</option> + </select> + </label> + <label>Hole diameter + <input type="text" name="hole_dia" placeholder="length" value="0.9"> + <span class="unit metric">mm</span> + <span class="unit us">mil</span> + </label> + <label>Pad shape + <select name="pad_shape" value="circle"> + <option value="circle">Circle</option> + <option value="rect">(Rounded) Rectangle</option> + <option value="obround">Obround</option> + </select> + </label> + <label class="only-shape rect">Corner radius + <input type="text" name="pad_h" placeholder="length" value="0"> + <span class="unit metric">mm</span> + <span class="unit us">mil</span> + </label> + </div> + </template> + + <template id="tpl-g-manhattan"> + <div data-type="manhattan" class="group manhattan"> + <h4>Manhattan area</h4> + <span class="content area-controls">(<a href="#" class="area-remove">Remove</a><a href="#" class="area-move">Move</a>)</span> + <label class="proportion">Proportion + <input type="text" name="layout_prop" value="1"> + </label> + + <h5>Area Settings</h5> + <label>Pitch X + <input type="text" name="pitch_x" placeholder="length" value="5.08"> + <span class="unit metric">mm</span> + <span class="unit us">mil</span> + </label> + <label>Pitch Y + <input type="text" name="pitch_y" placeholder="length" value="5.08"> + <span class="unit metric">mm</span> + <span class="unit us">mil</span> + </label> + <label>Clearance + <input type="text" name="clearance" placeholder="length" value="0.5"> + <span class="unit metric">mm</span> + <span class="unit us">mil</span> + </label> + </div> + </template> + + <template id="tpl-g-flower"> + <div data-type="flower" class="group flower"> + <h4>THT flower area</h4> + <span class="content area-controls">(<a href="#" class="area-remove">Remove</a><a href="#" class="area-move">Move</a>)</span> + <label class="proportion">Proportion + <input type="text" name="layout_prop" value="1"> + </label> + + <h5>Area Settings</h5> + <label>Pitch + <input type="text" name="pitch" placeholder="length" value="2.54"> + <span class="unit metric">mm</span> + <span class="unit us">mil</span> + </label> + <label>Pattern diameter + <input type="text" name="pattern_dia" placeholder="length" value="2.0"> + <span class="unit metric">mm</span> + <span class="unit us">mil</span> + </label> + <label>Hole diameter + <input type="text" name="hole_dia" placeholder="length" value="0.9"> + <span class="unit metric">mm</span> + <span class="unit us">mil</span> + </label> + <label>Clearance + <input type="text" name="clearance" placeholder="length" value="0.5"> + <span class="unit metric">mm</span> + <span class="unit us">mil</span> + </label> + </div> + </template> + + <template id="tpl-g-powered"> + <div data-type="powered" class="group powered"> + <h4>Powered THT area</h4> + <span class="content area-controls">(<a href="#" class="area-remove">Remove</a><a href="#" class="area-move">Move</a>)</span> + <label class="proportion">Proportion + <input type="text" name="layout_prop" value="1"> + </label> + + <h5>Area Settings</h5> + <label>Pitch + <input type="text" name="pitch" placeholder="length" value="2.54"> + <span class="unit metric">mm</span> + <span class="unit us">mil</span> + </label> + <label>Hole diameter + <input type="text" name="hole_dia" placeholder="length" value="0.9"> + <span class="unit metric">mm</span> + <span class="unit us">mil</span> + </label> + <label>Via drill + <input type="text" name="via_hole_dia" placeholder="length" value="0.9"> + <span class="unit metric">mm</span> + <span class="unit us">mil</span> + </label> + <label>Trace width + <input type="text" name="trace_width" placeholder="length" value="0.5"> + <span class="unit metric">mm</span> + <span class="unit us">mil</span> + </label> + <label>Clearance + <input type="text" name="clearance" placeholder="length" value="0.5"> + <span class="unit metric">mm</span> + <span class="unit us">mil</span> + </label> + </div> + </template> + + <template id="tpl-g-rf"> + <div data-type="rf" class="group rf"> + <h4>THT area with RF ground</h4> + <span class="content area-controls">(<a href="#" class="area-remove">Remove</a><a href="#" class="area-move">Move</a>)</span> + <label class="proportion">Proportion + <input type="text" name="layout_prop" value="1"> + </label> + + <h5>Area Settings</h5> + <label>Pitch + <input type="text" name="pitch" placeholder="length" value="2.54"> + <span class="unit metric">mm</span> + <span class="unit us">mil</span> + </label> + <label>Hole diameter + <input type="text" name="hole_dia" placeholder="length" value="0.9"> + <span class="unit metric">mm</span> + <span class="unit us">mil</span> + </label> + <label>Trace width + <input type="text" name="trace_width" placeholder="length" value="0.5"> + <span class="unit metric">mm</span> + <span class="unit us">mil</span> + </label> + <label>Via diameter + <input type="text" name="via_dia" placeholder="length" value="0.8"> + <span class="unit metric">mm</span> + <span class="unit us">mil</span> + </label> + <label>Via drill + <input type="text" name="via_hole_dia" placeholder="length" value="0.4"> + <span class="unit metric">mm</span> + <span class="unit us">mil</span> + </label> + <label>Clearance + <input type="text" name="clearance" placeholder="length" value="0.5"> + <span class="unit metric">mm</span> + <span class="unit us">mil</span> + </label> + </div> + </template> + + <script> + document.querySelectorAll('.expand').forEach((elem) => { + const checkbox = elem.querySelector(':first-child > input'); + checkbox.addEventListener("change", (evt) => { + if (evt.currentTarget.checked) { + elem.classList.remove('collapsed'); + } else { + elem.classList.add('collapsed'); + } + }); + if (checkbox.checked) { + elem.classList.remove('collapsed'); + } else { + elem.classList.add('collapsed'); + } + }); + + let g_dropElement = null; + + function hookupAreaRemove(node) { + for (const bt of node.querySelectorAll('a.area-remove')) { + bt.addEventListener('click', (evt) => { + let elem = evt.target.closest('.group'); + if (elem.parentElement && elem.parentElement.matches('.proportional')) { + let sibling = elem.previousElementSibling; + if (sibling.matches('.drop-target')) { + sibling.remove(); + } + elem.remove(); + + } else { + elem.replaceWith(createPlaceholder()); + } + + previewReloader.scheduleCall(); + }); + } + } + + function createDropTarget() { + const node = document.querySelector('#tpl-drop-target').content.cloneNode(true); + node.querySelector('a').addEventListener('click', (evt) => { + if (g_dropElement != null) { + const target = evt.target.closest('a'); + + let sibling = g_dropElement.previousElementSibling; + if (sibling.matches('.drop-target')) { + if (sibling == target) { + return; + } + sibling.remove(); + } + g_dropElement.remove(); + g_dropElement.querySelector('a.area-move').innerText = "Move"; + + target.before(sibling); + target.before(g_dropElement); + + document.querySelector('#controls').classList.remove('move-in-progress'); + document.querySelector('.group.drop-enabled').classList.remove('drop-enabled'); + g_dropElement = null; + + previewReloader.scheduleCall(); + } + }); + return node; + } + + function hookupAreaMove(node) { + for (const bt of node.querySelectorAll('a.area-move')) { + bt.addEventListener('click', (evt) => { + const controls = document.querySelector('#controls'); + const group = evt.target.closest('.group'); + + if (g_dropElement == null) { + controls.classList.add('move-in-progress'); + group.parentElement.classList.add('drop-enabled'); + g_dropElement = group; + evt.target.innerText = "Cancel move"; + + } else { + controls.classList.remove('move-in-progress'); + group.parentElement.classList.remove('drop-enabled'); + g_dropElement = null; + evt.target.innerText = "Move"; + } + }); + } + } + + function hookupPreviewUpdate(node) { + for (const elem of node.querySelectorAll('select, input')) { + elem.addEventListener('change', previewReloader.scheduleCall.bind(previewReloader)); + } + } + + function createLayoutItem(type) { + if (type == 'placeholder') { + return createPlaceholder(); + } + + const template = document.querySelector(`#tpl-g-${type}`); + const node = template.content.cloneNode(true).firstElementChild; + + hookupPreviewUpdate(node); + hookupAreaRemove(node); + hookupAreaMove(node); + + for (const bt of node.querySelectorAll(':scope > a.add-element')) { + bt.addEventListener('click', (evt) => { + evt.target.before(createPlaceholder()); + evt.target.before(createDropTarget()); + previewReloader.scheduleCall(); + }); + } + + function updateShapeFilter(filterNode) { + console.log(filterNode); + for (elem of filterNode.closest('.group').querySelectorAll('.only-shape')) { + if (elem.classList.contains(filterNode.value)) { + elem.style.removeProperty('display'); + } else { + elem.style.setProperty('display', 'none'); + } + } + } + + if (type == 'tht' || type == 'smd') { + const filterNode = node.querySelector('select[name="pad_shape"]'); + updateShapeFilter(filterNode); + filterNode.addEventListener('change', (evt) => { + updateShapeFilter(evt.target); + }); + } + + return node; + } + + function createPlaceholder() { + const node = document.querySelector('#tpl-g-placeholder').content.cloneNode(true).firstElementChild; + + hookupAreaRemove(node); + hookupAreaMove(node); + + for (const bt of node.querySelectorAll('.placeholder a[data-placeholder]')) { + bt.addEventListener('click', (evt) => { + const item = createLayoutItem(evt.target.getAttribute('data-placeholder')); + + for (const elem of item.querySelectorAll('div.placeholder')) { + elem.replaceWith(createPlaceholder()); + } + + for (const elem of item.querySelectorAll('div.drop-target')) { + elem.replaceWith(createDropTarget()); + } + + evt.target.closest('.group').replaceWith(item); + previewReloader.scheduleCall(); + }); + } + + return node; + } + + function serializeNode(node) { + function serializeProperties(node) { + let obj = {}; + for (const input of node.querySelectorAll(':scope > label > input, :scope > label > select')) { + if (input.type == 'checkbox') { + obj[input.name] = input.checked; + + } else { + obj[input.name] = input.value; + } + } + return obj; + } + + const obj = serializeProperties(node); + + for (const expand of node.querySelectorAll(':scope > .group.expand')) { + obj[expand.getAttribute('data-group')] = serializeProperties(expand); + } + + const children = []; + for (const elem of node.querySelectorAll(':scope > .group:not(.expand)')) { + const child = serializeNode(elem); + child['type'] = elem.getAttribute('data-type'); + children.push(child); + } + obj['children'] = children; + + return obj; + } + + function serialize() { + const board = document.querySelector('.group.board'); + return JSON.stringify(serializeNode(board)); + } + + + function deserializeNode(node, obj) { + function deserializeProperties(node, obj) { + for (const input of node.querySelectorAll(':scope > label > input, :scope > label > select')) { + if (input.type == 'checkbox') { + input.checked = obj[input.name]; + + } else { + input.value = obj[input.name]; + } + } + } + + deserializeProperties(node, obj); + + for (const expand of node.querySelectorAll(':scope > .group.expand')) { + deserializeProperties(expand, obj[expand.getAttribute('data-group')]); + } + + for (const child of obj['children']) { + const type = child['type']; + if (type) { + const item = createLayoutItem(type); + deserializeNode(item, child); + + if (type == 'layout') { + for (const elem of item.querySelectorAll('div.placeholder, div.drop-target')) { + elem.remove(); + } + + } else { + for (const elem of item.querySelectorAll('div.drop-target')) { + elem.replaceWith(createDropTarget()); + } + } + + if (obj['type'] == 'layout') { + const addLink = node.querySelector(':scope > a.add-element'); + addLink.before(item); + addLink.before(createDropTarget()); + + } else { + const placeholder = node.querySelector('div.placeholder'); + placeholder.replaceWith(item); + } + } + } + } + + function deserialize(json) { + const board = document.querySelector('.group.board'); + const data = JSON.parse(json); + deserializeNode(board, data); + previewReloader.scheduleCall(); + } + + class RateLimiter { + constructor(callback, interval_ms) { + this.callback = callback; + this.interval_ms = interval_ms; + this.lastRan = -1e99; + this.timerId = null; + } + + callNow() { + const now = performance.timeOrigin + performance.now(); + this.lastRan = now; + this.timerId = null; + this.callback(); + } + + scheduleCall() { + const now = performance.timeOrigin + performance.now(); + const timeRemaining = this.interval_ms - (now - this.lastRan); + console.log('scheduling', timeRemaining); + if (!this.timerId) { + if (timeRemaining <= 0) { + this.callNow(); + } else { + const callback = this.callback; + this.timerId = setTimeout(this.callNow.bind(this), timeRemaining); + } + } + } + } + + previewBlobURL = null; + previewReloader = new RateLimiter(async () => { + const response = await fetch('preview.svg', { + method: 'POST', + mode: 'same-origin', + cache: 'no-cache', + headers: {'Content-Type': 'application/json'}, + body: serialize(), + }); + const data = await response.blob(); + if (previewBlobURL) { + URL.revokeObjectURL(previewBlobURL); + } + previewBlobURL = URL.createObjectURL(data); + document.querySelector('#preview-image').src = previewBlobURL; + }, 1000); + + document.querySelector('div.placeholder').replaceWith(createPlaceholder()); + + for (elem of document.querySelectorAll('select[name="units"]')) { + elem.addEventListener('change', (evt) => { + const style = evt.target.closest('.group').style; + for (const unit of ['metric', 'us']) { + const value = (unit == evt.target.value) ? 'default' : 'none'; + style.setProperty(`--u-display-${unit}`, value); + } + }); + } + + hookupPreviewUpdate(document.querySelector('.group.board')); + previewReloader.scheduleCall(); + </script> + </body> +</html> |