summaryrefslogtreecommitdiff
path: root/gerbonara/cad/protoserve_data/protoserve.html
diff options
context:
space:
mode:
Diffstat (limited to 'gerbonara/cad/protoserve_data/protoserve.html')
-rw-r--r--gerbonara/cad/protoserve_data/protoserve.html899
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>