From 446b3b46fdf815f9b2d0c719bb3ef9f87c846cd6 Mon Sep 17 00:00:00 2001 From: Alexander Wainwright Date: Thu, 18 Dec 2025 17:19:16 +1000 Subject: [PATCH] Interim working solution --- index.html | 14 ++- script.js | 281 +++++++++++++++++++++++++++++++++-------------------- style.css | 23 +++++ 3 files changed, 212 insertions(+), 106 deletions(-) diff --git a/index.html b/index.html index 242124b..3743bfb 100644 --- a/index.html +++ b/index.html @@ -127,9 +127,21 @@
+
- + + + +
diff --git a/script.js b/script.js index f191aaf..620d68c 100644 --- a/script.js +++ b/script.js @@ -2,7 +2,13 @@ document.addEventListener('DOMContentLoaded', () => { // --- GLOBALS --- const qrContainer = document.getElementById('qr-container'); const downloadBtn = document.getElementById('btn-download'); - + const stickyWrapper = document.querySelector('.sticky-wrapper'); + + // Gauge Elements + const capMeter = document.getElementById('capacity-meter'); + const capBar = document.getElementById('capacity-bar'); + const capText = document.getElementById('capacity-text'); + // Inputs const modeSelector = document.getElementById('mode-selector'); const optEcc = document.getElementById('opt-ecc'); @@ -11,14 +17,14 @@ document.addEventListener('DOMContentLoaded', () => { 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 --- + // --- 1. DATA GATHERING --- function getFormat() { for (const r of fmtRadios) { @@ -27,36 +33,6 @@ document.addEventListener('DOMContentLoaded', () => { 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') { @@ -65,7 +41,8 @@ document.addEventListener('DOMContentLoaded', () => { 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 ''; + // return `WIFI:S:${ssid};T:${type};P:${pass};;`; + if (!ssid) return ''; const cleanSSID = ssid.replace(/([\\;,:])/g, '\\$1'); const cleanPass = pass.replace(/([\\;,:])/g, '\\$1'); return `WIFI:S:${cleanSSID};T:${type};P:${cleanPass};;`; @@ -79,133 +56,230 @@ document.addEventListener('DOMContentLoaded', () => { return ''; } - // The Custom SVG Renderer - // We read the grid data from the library and build a vector string + // --- 2. INSTANCE MANAGEMENT --- + + function createQRInstance() { + 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; + + // Update Labels (UI) + hexFg.textContent = colorDark; + hexBg.textContent = colorLight; + + try { + return new QRCode(qrContainer, { + width: size, + height: size, + colorDark : colorDark, + colorLight : colorLight, + correctLevel : ecc + }); + } catch (e) { + return null; + } + } + + // --- 3. RENDERING HELPERS --- + 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`; + pathData += `M${x},${y}h${modSize}v${modSize}h-${modSize}z`; } } } - const svg = ` + return ` `; - - return svg; } - function updateQR() { - if (!qrcodeObj) return; - const data = getDataString(); + // --- 4. CORE FUNCTIONS --- + + // A. The Fast One: Updates the bar immediately + function updateCapacityGauge(textData) { + if (!textData || !capMeter) { + if (capMeter) capMeter.style.display = 'none'; + return; + } + + const blob = new Blob([textData]); + const bytes = blob.size; + + const maxCapacityMap = { 'L': 2953, 'M': 2331, 'Q': 1663, 'H': 1273 }; + const maxBytes = maxCapacityMap[optEcc.value] || 1273; + + const usage = Math.min(100, Math.round((bytes / maxBytes) * 100)); + + if (usage > 5) { + capMeter.style.display = 'block'; + capBar.style.width = `${usage}%`; + capText.textContent = `${usage}% (${bytes}B)`; + + if (usage > 90) { + capBar.style.backgroundColor = 'red'; + capText.style.color = 'red'; + } else { + capBar.style.backgroundColor = '#000'; + capText.style.color = '#000'; + } + } else { + capMeter.style.display = 'none'; + } + } + + // B. The Heavy One: Generates the image (Debounced) + function generateQR(textData) { + // Reset Error State + stickyWrapper.classList.remove('has-error'); const format = getFormat(); - if (!data || data.trim() === '') { - qrcodeObj.clear(); - qrContainer.innerHTML = ''; // Clear SVG leftovers + // Handle Empty + if (!textData || textData.trim() === '') { + if (qrcodeObj) qrcodeObj.clear(); + qrcodeObj = null; + qrContainer.innerHTML = ''; downloadBtn.disabled = true; downloadBtn.textContent = `DOWNLOAD ${format.toUpperCase()}`; return; } - downloadBtn.disabled = false; + // Ensure Instance + if (!qrcodeObj) qrcodeObj = createQRInstance(); + if (!qrcodeObj) return; + downloadBtn.textContent = `DOWNLOAD ${format.toUpperCase()}`; - // 1. Let the library calculate the math - qrcodeObj.makeCode(data); + try { + // Generate + // qrcodeObj.clear() + qrcodeObj.makeCode(textData); - // 2. Handle Display based on format - if (format === 'svg') { - // Hide the canvas image generated by lib + // check if anything royally fucked up const imgs = qrContainer.querySelectorAll('img'); - imgs.forEach(i => i.style.display = 'none'); - const canvas = qrContainer.querySelectorAll('canvas'); - canvas.forEach(c => c.style.display = 'none'); + // if (imgs.length == 0) { + // console.log('==================== YIKES =====================') + // qrcodeObj = createQRInstance(); + // qrcodeObj.makeCode(textData) + // } - // 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); + // Render SVG vs PNG + if (format === 'svg') { + const imgs = qrContainer.querySelectorAll('img'); + imgs.forEach(i => i.style.display = 'none'); + const canvas = qrContainer.querySelectorAll('canvas'); + canvas.forEach(c => c.style.display = 'none'); - // Remove old SVG if exists - const oldSvg = qrContainer.querySelector('svg'); - if (oldSvg) oldSvg.remove(); - - // Inject new SVG - qrContainer.insertAdjacentHTML('beforeend', svgString); + if (qrcodeObj._oQRCode && qrcodeObj._oQRCode.modules) { + const svgString = renderSVG(qrcodeObj._oQRCode.modules); + // Clear old SVGs/Errors + qrContainer.querySelectorAll('svg, .qr-error-msg').forEach(e => e.remove()); + qrContainer.insertAdjacentHTML('beforeend', svgString); + } + } else { + // PNG Mode + const imgs = qrContainer.querySelectorAll('img'); + imgs.forEach(i => i.style.display = 'block'); + qrContainer.querySelectorAll('svg, .qr-error-msg').forEach(e => e.remove()); } - } 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(); + downloadBtn.disabled = false; + + } catch (e) { + // Error Handling + // console.log(e) + qrcodeObj = null; + qrContainer.innerHTML = ` +
+ ERROR: CAPACITY EXCEEDED
+ + REDUCE TEXT OR
LOWER ECC LEVEL +
+
+ `; + downloadBtn.disabled = true; + downloadBtn.textContent = "GENERATION FAILED"; } } + // --- 5. THE COORDINATOR --- + + function handleInput() { + console.log("Input detected. Text length:", getDataString().length); + const text = getDataString(); + console.log(text) + + // 1. Instant Feedback + updateCapacityGauge(text); + + // 2. Delayed Heavy Lifting + clearTimeout(debounceTimer); + debounceTimer = setTimeout(() => { + generateQR(text); + }, 300); + } + + // Force Immediate Update (for Config changes) + function forceUpdate() { + const text = getDataString(); + updateCapacityGauge(text); + generateQR(text); + } + // --- EVENT LISTENERS --- - // 1. Inputs triggering update + // Config Changes (Require Rebuild) [optEcc, optSize, optFg, optBg].forEach(el => { el.addEventListener('input', () => { + // Debounce the rebuild slightly clearTimeout(debounceTimer); - debounceTimer = setTimeout(createQRInstance, 100); + debounceTimer = setTimeout(() => { + qrcodeObj = null; + forceUpdate(); + }, 100); }); }); - // Format Change - fmtRadios.forEach(r => { - r.addEventListener('change', updateQR); - }); + // Format Change (Instant) + fmtRadios.forEach(r => r.addEventListener('change', forceUpdate)); - // Mode Switch + // Mode Switch (Instant) 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(); + forceUpdate(); }); - // Data Entry + // DATA ENTRY (The optimized path) document.querySelectorAll('.input-form').forEach(form => { - form.addEventListener('input', () => { - clearTimeout(debounceTimer); - debounceTimer = setTimeout(updateQR, 300); - }); + form.addEventListener('input', handleInput); }); - // 2. Download Logic + // Download 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'); @@ -213,28 +287,25 @@ document.addEventListener('DOMContentLoaded', () => { link.download = `qr-manifesto-${Date.now()}.png`; document.body.appendChild(link); link.click(); - document.body.removeChild(link); + link.remove(); } } 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); + link.remove(); } } }); - // Init - createQRInstance(); + // Initial Start + forceUpdate(); }); diff --git a/style.css b/style.css index 4ce8b91..67c844e 100644 --- a/style.css +++ b/style.css @@ -445,3 +445,26 @@ input[type="radio"]:checked { input[type="radio"]:focus { outline: none; } + +/* In-Box Error Message */ +.qr-error-msg { + color: red; + font-family: var(--font-mono); + font-weight: 700; + text-align: center; + text-transform: uppercase; + font-size: 0.9rem; + line-height: 1.4; + + /* Brutalist Box inside the Box */ + border: 2px solid red; + padding: 1rem; + background-color: #fff; + width: 80%; /* Don't touch the edges */ +} + +/* Ensure the container keeps its shape even when showing error */ +#qr-container { + /* Existing styles... */ + position: relative; /* Just in case */ +}