diff --git a/index.html b/index.html index 3743bfb..bd599b7 100644 --- a/index.html +++ b/index.html @@ -34,6 +34,16 @@ + +
@@ -128,20 +138,9 @@
-
-
+
- -
diff --git a/script.js b/script.js index 620d68c..c91a53b 100644 --- a/script.js +++ b/script.js @@ -1,5 +1,5 @@ document.addEventListener('DOMContentLoaded', () => { - // --- GLOBALS --- + // --- 1. DOM REFERENCES --- const qrContainer = document.getElementById('qr-container'); const downloadBtn = document.getElementById('btn-download'); const stickyWrapper = document.querySelector('.sticky-wrapper'); @@ -8,6 +8,7 @@ document.addEventListener('DOMContentLoaded', () => { const capMeter = document.getElementById('capacity-meter'); const capBar = document.getElementById('capacity-bar'); const capText = document.getElementById('capacity-text'); + const capLabel = document.getElementById('capacity-label'); // Inputs const modeSelector = document.getElementById('mode-selector'); @@ -18,13 +19,11 @@ document.addEventListener('DOMContentLoaded', () => { 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; - // --- 1. DATA GATHERING --- + // --- 2. STATE GETTERS (Pure Functions) --- function getFormat() { for (const r of fmtRadios) { @@ -41,7 +40,7 @@ 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; - // return `WIFI:S:${ssid};T:${type};P:${pass};;`; + // Note: Even if empty, we return the structure so the logic can decide if (!ssid) return ''; const cleanSSID = ssid.replace(/([\\;,:])/g, '\\$1'); const cleanPass = pass.replace(/([\\;,:])/g, '\\$1'); @@ -56,42 +55,9 @@ document.addEventListener('DOMContentLoaded', () => { return ''; } - // --- 2. INSTANCE MANAGEMENT --- + // --- 3. RENDER HELPERS --- - 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; + function generateSVGString(modules, size, fg, bg) { const count = modules.length; const modSize = size / count; @@ -114,27 +80,50 @@ document.addEventListener('DOMContentLoaded', () => { `; } - // --- 4. CORE FUNCTIONS --- + function renderError() { + qrContainer.innerHTML = ` +
+ ERROR: CAPACITY EXCEEDED
+ + REDUCE TEXT OR
LOWER ECC LEVEL +
+
+ `; + downloadBtn.disabled = true; + downloadBtn.textContent = "GENERATION FAILED"; + stickyWrapper.classList.add('has-error'); + } - // A. The Fast One: Updates the bar immediately - function updateCapacityGauge(textData) { - if (!textData || !capMeter) { - if (capMeter) capMeter.style.display = 'none'; + // --- 4. CORE LOGIC --- + + // A. Capacity Gauge (Fast / Instant) + function updateCapacityUI(textData) { + if (!textData) { + 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; + // Limits based on Version 40 (Byte Mode) + const maxCapacityMap = { 'L': 2950, 'M': 2328, 'Q': 1660, 'H': 1270 }; + const maxBytes = maxCapacityMap[optEcc.value] || 1270; - const usage = Math.min(100, Math.round((bytes / maxBytes) * 100)); + const raw_usage = Math.round((bytes / maxBytes) * 100); + const usage = Math.min(100, raw_usage); - if (usage > 5) { + if (usage > 50) { capMeter.style.display = 'block'; capBar.style.width = `${usage}%`; - capText.textContent = `${usage}% (${bytes}B)`; + + capText.textContent = `${usage}% (${bytes} / ${maxBytes} B)`; + + if (raw_usage <= 100) { + capLabel.textContent = 'CAPACITY'; + } else { + capLabel.textContent = 'OVER CAPACITY'; + } if (usage > 90) { capBar.style.backgroundColor = 'red'; @@ -148,137 +137,127 @@ document.addEventListener('DOMContentLoaded', () => { } } - // B. The Heavy One: Generates the image (Debounced) - function generateQR(textData) { - // Reset Error State - stickyWrapper.classList.remove('has-error'); + // B. The Main Renderer (Stateless: Flush and Rebuild) + function renderQR() { + // 1. Gather State + const textData = getDataString(); const format = getFormat(); + const ecc = QRCode.CorrectLevel[optEcc.value]; + const colorDark = optFg.value; + const colorLight = optBg.value; + + // Update Hex Labels + hexFg.textContent = colorDark; + hexBg.textContent = colorLight; - // Handle Empty + // 2. Reset DOM + qrContainer.innerHTML = ''; + stickyWrapper.classList.remove('has-error'); + + // 3. Handle Empty State if (!textData || textData.trim() === '') { - if (qrcodeObj) qrcodeObj.clear(); - qrcodeObj = null; - qrContainer.innerHTML = ''; downloadBtn.disabled = true; downloadBtn.textContent = `DOWNLOAD ${format.toUpperCase()}`; - return; + return; // Stop here. Container is clean. } - // Ensure Instance - if (!qrcodeObj) qrcodeObj = createQRInstance(); - if (!qrcodeObj) return; - downloadBtn.textContent = `DOWNLOAD ${format.toUpperCase()}`; + // 4. Validate Size (Prevent Crashes) + let size = parseInt(optSize.value) || 256; + if (size < 64) size = 64; + if (size > 4000) size = 4000; + + // 5. Generate try { - // Generate - // qrcodeObj.clear() - qrcodeObj.makeCode(textData); + // We create a local instance. It populates qrContainer with a Canvas/Img. + // We do not save this instance globally. It is single-use. + const instance = new QRCode(qrContainer, { + text: textData, // Passing text here triggers makeCode immediately + width: size, + height: size, + colorDark : colorDark, + colorLight : colorLight, + correctLevel : ecc + }); - // check if anything royally fucked up - const imgs = qrContainer.querySelectorAll('img'); - // if (imgs.length == 0) { - // console.log('==================== YIKES =====================') - // qrcodeObj = createQRInstance(); - // qrcodeObj.makeCode(textData) - // } - - // Render SVG vs PNG + // 6. Post-Process (Swap to SVG if needed) 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'); - - 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); + // The library just painted a canvas. We hide it and inject SVG. + // We use the internal data of our local 'instance' + const modules = instance._oQRCode.modules; + + // Hide library output + const nodes = qrContainer.childNodes; + for(let i=0; i i.style.display = 'block'); - qrContainer.querySelectorAll('svg, .qr-error-msg').forEach(e => e.remove()); + + // Inject SVG + const svgString = generateSVGString(modules, size, colorDark, colorLight); + qrContainer.insertAdjacentHTML('beforeend', svgString); + } + else { + // PNG Mode: The library already appended an . We are done. + // Just ensure it's visible (library defaults to display:none sometimes for the img) + const img = qrContainer.querySelector('img'); + if(img) img.style.display = 'block'; } 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"; + console.log(e) + // 7. Handle Overflow/Errors + // Since we flushed the DOM at the start, we can just render the error now. + renderError(); } } - // --- 5. THE COORDINATOR --- + // --- 5. EVENT ORCHESTRATION --- - function handleInput() { - console.log("Input detected. Text length:", getDataString().length); + function handleUpdate(immediate = false) { const text = getDataString(); - console.log(text) - // 1. Instant Feedback - updateCapacityGauge(text); - - // 2. Delayed Heavy Lifting + // Always update gauge immediately + updateCapacityUI(text); + + // Debounce the heavy rendering clearTimeout(debounceTimer); - debounceTimer = setTimeout(() => { - generateQR(text); - }, 300); + if (immediate) { + renderQR(); + } else { + debounceTimer = setTimeout(renderQR, 300); + } } - // Force Immediate Update (for Config changes) - function forceUpdate() { - const text = getDataString(); - updateCapacityGauge(text); - generateQR(text); - } + // --- 6. LISTENERS --- - // --- EVENT LISTENERS --- - - // Config Changes (Require Rebuild) + // Configuration Inputs (Colors, ECC, Size) -> Trigger Rebuild [optEcc, optSize, optFg, optBg].forEach(el => { - el.addEventListener('input', () => { - // Debounce the rebuild slightly - clearTimeout(debounceTimer); - debounceTimer = setTimeout(() => { - qrcodeObj = null; - forceUpdate(); - }, 100); - }); + el.addEventListener('input', () => handleUpdate(false)); }); - // Format Change (Instant) - fmtRadios.forEach(r => r.addEventListener('change', forceUpdate)); + // Format Change -> Trigger Rebuild (Instant) + fmtRadios.forEach(r => r.addEventListener('change', () => handleUpdate(true))); - // Mode Switch (Instant) + // Mode Switch -> Change Form & Rebuild 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'); - forceUpdate(); + handleUpdate(true); }); - // DATA ENTRY (The optimized path) + // Data Entry -> Debounced Rebuild document.querySelectorAll('.input-form').forEach(form => { - form.addEventListener('input', handleInput); + form.addEventListener('input', () => handleUpdate(false)); }); - // Download + // Download Handler downloadBtn.addEventListener('click', () => { const format = getFormat(); + if (format === 'png') { const img = qrContainer.querySelector('img'); if (img && img.src) { @@ -306,6 +285,6 @@ document.addEventListener('DOMContentLoaded', () => { } }); - // Initial Start - forceUpdate(); + // Initial Render + handleUpdate(true); });