Files
qrdamage/script.js
Alexander Wainwright c3fdf45015 Add SVG option
2025-12-17 16:58:35 +10:00

241 lines
8.4 KiB
JavaScript

document.addEventListener('DOMContentLoaded', () => {
// --- GLOBALS ---
const qrContainer = document.getElementById('qr-container');
const downloadBtn = document.getElementById('btn-download');
// Inputs
const modeSelector = document.getElementById('mode-selector');
const optEcc = document.getElementById('opt-ecc');
const optSize = document.getElementById('opt-size');
const optFg = document.getElementById('opt-fg');
const optBg = document.getElementById('opt-bg');
const hexFg = document.getElementById('hex-fg');
const hexBg = document.getElementById('hex-bg');
// Format Radios
const fmtRadios = document.getElementsByName('opt-fmt');
let qrcodeObj = null;
let debounceTimer;
// --- LOGIC ---
function getFormat() {
for (const r of fmtRadios) {
if (r.checked) return r.value;
}
return 'png';
}
function createQRInstance() {
// We clean the container but keep the instance variable if we can
qrContainer.innerHTML = '';
let size = parseInt(optSize.value);
if (isNaN(size) || size < 64) size = 64;
if (size > 4000) size = 4000;
const ecc = QRCode.CorrectLevel[optEcc.value];
const colorDark = optFg.value;
const colorLight = optBg.value;
hexFg.textContent = colorDark;
hexBg.textContent = colorLight;
// Initialize the library
// Note: The library always creates a canvas internally.
// We will just hide it if we are in SVG mode.
qrcodeObj = new QRCode(qrContainer, {
width: size,
height: size,
colorDark : colorDark,
colorLight : colorLight,
correctLevel : ecc
});
// Force update to draw
updateQR();
}
function getDataString() {
const mode = modeSelector.value;
if (mode === 'text') {
return document.getElementById('inp-text').value;
} else if (mode === 'wifi') {
const ssid = document.getElementById('inp-wifi-ssid').value;
const pass = document.getElementById('inp-wifi-pass').value;
const type = document.getElementById('inp-wifi-type').value;
if (!ssid) return '';
const cleanSSID = ssid.replace(/([\\;,:])/g, '\\$1');
const cleanPass = pass.replace(/([\\;,:])/g, '\\$1');
return `WIFI:S:${cleanSSID};T:${type};P:${cleanPass};;`;
} else if (mode === 'email') {
const to = document.getElementById('inp-email-to').value;
const sub = document.getElementById('inp-email-sub').value;
const body = document.getElementById('inp-email-body').value;
if (!to) return '';
return `mailto:${to}?subject=${encodeURIComponent(sub)}&body=${encodeURIComponent(body)}`;
}
return '';
}
// The Custom SVG Renderer
// We read the grid data from the library and build a vector string
function renderSVG(modules) {
const size = parseInt(optSize.value) || 256;
const fg = optFg.value;
const bg = optBg.value;
const count = modules.length;
// Calculate module (pixel) size
const modSize = size / count;
let pathData = '';
// Loop through the matrix
for (let r = 0; r < count; r++) {
for (let c = 0; c < count; c++) {
if (modules[r][c]) {
// It's a black dot. Draw a rect.
// To prevent "hairline cracks" between blocks in some viewers,
// we can slightly overlap or just use standard math.
// Standard math is usually fine for SVGs.
const x = c * modSize;
const y = r * modSize;
// Using 'h' and 'v' in path is more efficient than rects
pathData += `M${x},${y}h${modSize}v${modSize}h-${modSize}z`;
}
}
}
const svg = `
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${size} ${size}" width="${size}" height="${size}">
<rect width="100%" height="100%" fill="${bg}"/>
<path d="${pathData}" fill="${fg}" shape-rendering="crispEdges"/>
</svg>
`;
return svg;
}
function updateQR() {
if (!qrcodeObj) return;
const data = getDataString();
const format = getFormat();
if (!data || data.trim() === '') {
qrcodeObj.clear();
qrContainer.innerHTML = ''; // Clear SVG leftovers
downloadBtn.disabled = true;
downloadBtn.textContent = `DOWNLOAD ${format.toUpperCase()}`;
return;
}
downloadBtn.disabled = false;
downloadBtn.textContent = `DOWNLOAD ${format.toUpperCase()}`;
// 1. Let the library calculate the math
qrcodeObj.makeCode(data);
// 2. Handle Display based on format
if (format === 'svg') {
// Hide the canvas image generated by lib
const imgs = qrContainer.querySelectorAll('img');
imgs.forEach(i => i.style.display = 'none');
const canvas = qrContainer.querySelectorAll('canvas');
canvas.forEach(c => c.style.display = 'none');
// Access internal data to build SVG
// Safety check: _oQRCode is the internal object of qrcode.js
if (qrcodeObj._oQRCode && qrcodeObj._oQRCode.modules) {
const svgString = renderSVG(qrcodeObj._oQRCode.modules);
// Remove old SVG if exists
const oldSvg = qrContainer.querySelector('svg');
if (oldSvg) oldSvg.remove();
// Inject new SVG
qrContainer.insertAdjacentHTML('beforeend', svgString);
}
} else {
// PNG Mode: Ensure canvas/img is visible
// The library handles the rest
const imgs = qrContainer.querySelectorAll('img');
imgs.forEach(i => i.style.display = 'block');
// Remove SVG if exists
const oldSvg = qrContainer.querySelector('svg');
if (oldSvg) oldSvg.remove();
}
}
// --- EVENT LISTENERS ---
// 1. Inputs triggering update
[optEcc, optSize, optFg, optBg].forEach(el => {
el.addEventListener('input', () => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(createQRInstance, 100);
});
});
// Format Change
fmtRadios.forEach(r => {
r.addEventListener('change', updateQR);
});
// Mode Switch
modeSelector.addEventListener('change', (e) => {
const newMode = e.target.value;
document.querySelectorAll('.input-form').forEach(f => f.classList.remove('active'));
document.getElementById(`form-${newMode}`).classList.add('active');
updateQR();
});
// Data Entry
document.querySelectorAll('.input-form').forEach(form => {
form.addEventListener('input', () => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(updateQR, 300);
});
});
// 2. Download Logic
downloadBtn.addEventListener('click', () => {
const format = getFormat();
if (format === 'png') {
// PNG Download
const img = qrContainer.querySelector('img');
if (img && img.src) {
const link = document.createElement('a');
link.href = img.src;
link.download = `qr-manifesto-${Date.now()}.png`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
} else {
// SVG Download
const svgEl = qrContainer.querySelector('svg');
if (svgEl) {
// Serialize the SVG DOM to a string
const serializer = new XMLSerializer();
const svgString = serializer.serializeToString(svgEl);
const blob = new Blob([svgString], {type: 'image/svg+xml;charset=utf-8'});
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `qr-manifesto-${Date.now()}.svg`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}
});
// Init
createQRInstance();
});