Increase font size ranges; add save/load design as JSON
This commit is contained in:
+88
-3
@@ -291,7 +291,7 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<label>Number font size</label>
|
||||
<input type="range" id="numFontSize" min="1" max="8" value="3" step="0.5">
|
||||
<input type="range" id="numFontSize" min="1" max="25" value="3" step="0.5">
|
||||
<span class="val" id="numFontSizeVal">3%</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -363,7 +363,7 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<label>Title size %</label>
|
||||
<input type="range" id="titleSize" min="2" max="15" value="6">
|
||||
<input type="range" id="titleSize" min="2" max="30" value="6">
|
||||
<span class="val" id="titleSizeVal">6%</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
@@ -377,7 +377,7 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<label>Subtitle size %</label>
|
||||
<input type="range" id="subtitleSize" min="1" max="10" value="3">
|
||||
<input type="range" id="subtitleSize" min="1" max="25" value="3">
|
||||
<span class="val" id="subtitleSizeVal">3%</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
@@ -484,6 +484,10 @@
|
||||
<div class="hint" style="margin-top:6px;text-align:center">
|
||||
PDF prints at exact physical size.<br>Print at 100% — no scaling.
|
||||
</div>
|
||||
<hr class="divider" style="margin-top:10px">
|
||||
<button id="saveDesignBtn" class="secondary">Save Design (.json)</button>
|
||||
<button id="loadDesignBtn" class="secondary">Load Design (.json)</button>
|
||||
<input type="file" id="loadDesignInput" accept=".json" style="display:none">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1018,6 +1022,87 @@ $('exportPngBtn').addEventListener('click', () => {
|
||||
link.click();
|
||||
});
|
||||
|
||||
// ── save / load design ────────────────────────────────────────────────────────
|
||||
$('saveDesignBtn').addEventListener('click', () => {
|
||||
const data = { version: 1, gaugeMode, sweepDir, zones: JSON.parse(JSON.stringify(zones)) };
|
||||
document.querySelectorAll('#sidebar input, #sidebar select').forEach(el => {
|
||||
if (!el.id || el.type === 'file') return;
|
||||
data[el.id] = el.type === 'checkbox' ? el.checked : el.value;
|
||||
});
|
||||
if (logoImg) {
|
||||
data.logoDataUrl = logoImg.src;
|
||||
data.logoFileName = $('logoFileName').textContent;
|
||||
}
|
||||
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
||||
const a = document.createElement('a');
|
||||
a.href = URL.createObjectURL(blob);
|
||||
a.download = `${$('gaugeTitle').value || 'gauge'}-design.json`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(a.href);
|
||||
});
|
||||
|
||||
$('loadDesignBtn').addEventListener('click', () => $('loadDesignInput').click());
|
||||
|
||||
$('loadDesignInput').addEventListener('change', e => {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
const reader = new FileReader();
|
||||
reader.onload = ev => {
|
||||
let data;
|
||||
try { data = JSON.parse(ev.target.result); } catch { alert('Invalid design file.'); return; }
|
||||
|
||||
// restore all inputs silently (no events yet)
|
||||
document.querySelectorAll('#sidebar input, #sidebar select').forEach(el => {
|
||||
if (!el.id || el.type === 'file' || !(el.id in data)) return;
|
||||
if (el.type === 'checkbox') el.checked = data[el.id];
|
||||
else el.value = data[el.id];
|
||||
});
|
||||
|
||||
// update range display spans by firing input events on ranges
|
||||
document.querySelectorAll('#sidebar input[type=range]').forEach(el => {
|
||||
el.dispatchEvent(new Event('input'));
|
||||
});
|
||||
|
||||
// restore gauge mode UI
|
||||
gaugeMode = data.gaugeMode || 'standard';
|
||||
if (gaugeMode === 'center') {
|
||||
$('modeCtr').classList.add('active'); $('modeStd').classList.remove('active');
|
||||
$('ctrFields').style.display = ''; $('stdFields').style.display = 'none';
|
||||
$('ctrTickNote').style.display = ''; $('stdTickNote').style.display = 'none';
|
||||
$('stdMajorInterval').style.display = 'none';
|
||||
} else {
|
||||
$('modeStd').classList.add('active'); $('modeCtr').classList.remove('active');
|
||||
$('stdFields').style.display = ''; $('ctrFields').style.display = 'none';
|
||||
$('stdTickNote').style.display = ''; $('ctrTickNote').style.display = 'none';
|
||||
$('stdMajorInterval').style.display = '';
|
||||
}
|
||||
|
||||
// restore sweep direction UI
|
||||
sweepDir = data.sweepDir || 'cw';
|
||||
if (sweepDir === 'ccw') {
|
||||
$('dirCCW').classList.add('active'); $('dirCW').classList.remove('active');
|
||||
} else {
|
||||
$('dirCW').classList.add('active'); $('dirCCW').classList.remove('active');
|
||||
}
|
||||
|
||||
// restore zones
|
||||
zones = data.zones || [];
|
||||
renderZones();
|
||||
|
||||
// restore logo
|
||||
if (data.logoDataUrl) {
|
||||
const img = new Image();
|
||||
img.onload = () => { setLogo(img, data.logoFileName || 'logo'); drawGauge(); };
|
||||
img.src = data.logoDataUrl;
|
||||
} else {
|
||||
clearLogo();
|
||||
drawGauge();
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
e.target.value = '';
|
||||
});
|
||||
|
||||
// ── preview background toggle ─────────────────────────────────────────────────
|
||||
const previewArea = $('preview-area');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user