Add reset button

This commit is contained in:
Alexander Wainwright
2025-12-18 18:30:58 +10:00
parent 6ef65c87a0
commit 205876a28f
2 changed files with 60 additions and 49 deletions

View File

@@ -133,13 +133,16 @@
</div> </div>
</div> </div>
</details> </details>
<button id="btn-clear" style="margin-top: 2rem; background: transparent; border: 1px solid black; color: black;">
RESET
</button>
</section> </section>
<section class="pane preview-pane"> <section class="pane preview-pane">
<div class="sticky-wrapper"> <div class="sticky-wrapper">
<div id="qr-container"> </div> <div id="qr-container"> </div>
<button id="btn-download" disabled>DOWNLOAD PNG</button> <button id="btn-download" disabled>DOWNLOAD PNG</button>
</div> </div>
</section> </section>

102
script.js
View File

@@ -3,6 +3,7 @@ document.addEventListener('DOMContentLoaded', () => {
const qrContainer = document.getElementById('qr-container'); const qrContainer = document.getElementById('qr-container');
const downloadBtn = document.getElementById('btn-download'); const downloadBtn = document.getElementById('btn-download');
const stickyWrapper = document.querySelector('.sticky-wrapper'); const stickyWrapper = document.querySelector('.sticky-wrapper');
const clearBtn = document.getElementById('btn-clear');
// Gauge Elements // Gauge Elements
const capMeter = document.getElementById('capacity-meter'); const capMeter = document.getElementById('capacity-meter');
@@ -23,7 +24,7 @@ document.addEventListener('DOMContentLoaded', () => {
let debounceTimer; let debounceTimer;
// --- 2. STATE GETTERS (Pure Functions) --- // --- 2. STATE GETTERS ---
function getFormat() { function getFormat() {
for (const r of fmtRadios) { for (const r of fmtRadios) {
@@ -40,7 +41,6 @@ document.addEventListener('DOMContentLoaded', () => {
const ssid = document.getElementById('inp-wifi-ssid').value; const ssid = document.getElementById('inp-wifi-ssid').value;
const pass = document.getElementById('inp-wifi-pass').value; const pass = document.getElementById('inp-wifi-pass').value;
const type = document.getElementById('inp-wifi-type').value; const type = document.getElementById('inp-wifi-type').value;
// Note: Even if empty, we return the structure so the logic can decide
if (!ssid) return ''; if (!ssid) return '';
const cleanSSID = ssid.replace(/([\\;,:])/g, '\\$1'); const cleanSSID = ssid.replace(/([\\;,:])/g, '\\$1');
const cleanPass = pass.replace(/([\\;,:])/g, '\\$1'); const cleanPass = pass.replace(/([\\;,:])/g, '\\$1');
@@ -60,7 +60,6 @@ document.addEventListener('DOMContentLoaded', () => {
function generateSVGString(modules, size, fg, bg) { function generateSVGString(modules, size, fg, bg) {
const count = modules.length; const count = modules.length;
const modSize = size / count; const modSize = size / count;
let pathData = ''; let pathData = '';
for (let r = 0; r < count; r++) { for (let r = 0; r < count; r++) {
for (let c = 0; c < count; c++) { for (let c = 0; c < count; c++) {
@@ -71,7 +70,6 @@ document.addEventListener('DOMContentLoaded', () => {
} }
} }
} }
return ` return `
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${size} ${size}" width="${size}" height="${size}"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${size} ${size}" width="${size}" height="${size}">
<rect width="100%" height="100%" fill="${bg}"/> <rect width="100%" height="100%" fill="${bg}"/>
@@ -94,15 +92,11 @@ document.addEventListener('DOMContentLoaded', () => {
stickyWrapper.classList.add('has-error'); stickyWrapper.classList.add('has-error');
} }
// --- 4. CORE LOGIC ---
// A. Capacity Gauge (Fast / Instant)
function updateCapacityUI(textData) { function updateCapacityUI(textData) {
if (!textData) { if (!textData) {
capMeter.style.display = 'none'; capMeter.style.display = 'none';
return; return;
} }
const blob = new Blob([textData]); const blob = new Blob([textData]);
const bytes = blob.size; const bytes = blob.size;
@@ -139,9 +133,9 @@ document.addEventListener('DOMContentLoaded', () => {
} }
} }
// B. The Main Renderer (Stateless: Flush and Rebuild) // --- 4. CORE RENDERER ---
function renderQR() { function renderQR() {
// 1. Gather State
const textData = getDataString(); const textData = getDataString();
const format = getFormat(); const format = getFormat();
const ecc = QRCode.CorrectLevel[optEcc.value]; const ecc = QRCode.CorrectLevel[optEcc.value];
@@ -151,11 +145,9 @@ document.addEventListener('DOMContentLoaded', () => {
hexFg.textContent = colorDark; hexFg.textContent = colorDark;
hexBg.textContent = colorLight; hexBg.textContent = colorLight;
// 2. Reset DOM
qrContainer.innerHTML = ''; qrContainer.innerHTML = '';
stickyWrapper.classList.remove('has-error'); stickyWrapper.classList.remove('has-error');
// 3. Handle Empty State
if (!textData || textData.trim() === '') { if (!textData || textData.trim() === '') {
downloadBtn.disabled = true; downloadBtn.disabled = true;
downloadBtn.textContent = `DOWNLOAD ${format.toUpperCase()}`; downloadBtn.textContent = `DOWNLOAD ${format.toUpperCase()}`;
@@ -164,12 +156,10 @@ document.addEventListener('DOMContentLoaded', () => {
downloadBtn.textContent = `DOWNLOAD ${format.toUpperCase()}`; downloadBtn.textContent = `DOWNLOAD ${format.toUpperCase()}`;
// 4. Validate Size
let size = parseInt(optSize.value) || 256; let size = parseInt(optSize.value) || 256;
if (size < 64) size = 64; if (size < 64) size = 64;
if (size > 4000) size = 4000; if (size > 4000) size = 4000;
// 5. Generate
try { try {
const instance = new QRCode(qrContainer, { const instance = new QRCode(qrContainer, {
text: textData, text: textData,
@@ -180,7 +170,6 @@ document.addEventListener('DOMContentLoaded', () => {
correctLevel : ecc correctLevel : ecc
}); });
// 6. Post-Process
if (format === 'svg') { if (format === 'svg') {
const modules = instance._oQRCode.modules; const modules = instance._oQRCode.modules;
const nodes = qrContainer.childNodes; const nodes = qrContainer.childNodes;
@@ -194,47 +183,26 @@ document.addEventListener('DOMContentLoaded', () => {
const img = qrContainer.querySelector('img'); const img = qrContainer.querySelector('img');
if(img) img.style.display = 'block'; if(img) img.style.display = 'block';
} }
downloadBtn.disabled = false; downloadBtn.disabled = false;
} catch (e) { } catch (e) {
// 7. Smart Error Handling
// Calculate usage to guess the error type
const blob = new Blob([textData]); const blob = new Blob([textData]);
const bytes = blob.size; const bytes = blob.size;
const maxCapacityMap = { 'L': 2953, 'M': 2331, 'Q': 1663, 'H': 1273 }; const maxCapacityMap = { 'L': 2953, 'M': 2331, 'Q': 1663, 'H': 1273 };
const maxBytes = maxCapacityMap[optEcc.value] || 1273; const maxBytes = maxCapacityMap[optEcc.value] || 1273;
// If we are over (or extremely close to) the limit, it's definitely a capacity issue.
// (We use >= because sometimes overhead pushes it over even if bytes == maxBytes)
if (bytes >= maxBytes) { if (bytes >= maxBytes) {
renderError( renderError("CAPACITY EXCEEDED", "REDUCE TEXT OR LOWER ECC LEVEL");
"CAPACITY EXCEEDED", } else {
"REDUCE TEXT OR LOWER ECC LEVEL" console.error(e);
); renderError("UNKNOWN ERROR", `CODE: ${e.name || 'Except'}`);
}
else {
// If usage is low but it crashed, it's a real bug (e.g. invalid char code).
// Show the specific error for debugging.
console.error(e); // Keep looking at console for devs
renderError(
"UNKNOWN ERROR",
`CODE: ${e.name || 'Except'} // ${e.message || 'Check Console'}`
);
} }
} }
} }
// --- 5. EVENT ORCHESTRATION ---
function handleUpdate(immediate = false) { function handleUpdate(immediate = false) {
const text = getDataString(); const text = getDataString();
// Always update gauge immediately
updateCapacityUI(text); updateCapacityUI(text);
// Debounce the heavy rendering
clearTimeout(debounceTimer); clearTimeout(debounceTimer);
if (immediate) { if (immediate) {
renderQR(); renderQR();
@@ -243,33 +211,58 @@ document.addEventListener('DOMContentLoaded', () => {
} }
} }
// --- 5. RESET LOGIC (Button Only) ---
function resetApp() {
// Clear Inputs
const inputs = document.querySelectorAll('.input-form input, .input-form textarea');
inputs.forEach(el => el.value = '');
// Reset Mode to Text
modeSelector.value = 'text';
// Update UI Classes
document.querySelectorAll('.input-form').forEach(f => f.classList.remove('active'));
document.getElementById('form-text').classList.add('active');
// Update Output
handleUpdate(true);
// Focus
const firstField = document.querySelector('#form-text input');
if (firstField) firstField.focus();
}
// --- 6. LISTENERS --- // --- 6. LISTENERS ---
// Configuration Inputs (Colors, ECC, Size) -> Trigger Rebuild
[optEcc, optSize, optFg, optBg].forEach(el => { [optEcc, optSize, optFg, optBg].forEach(el => {
el.addEventListener('input', () => handleUpdate(false)); el.addEventListener('input', () => handleUpdate(false));
}); });
// Format Change -> Trigger Rebuild (Instant)
fmtRadios.forEach(r => r.addEventListener('change', () => handleUpdate(true))); fmtRadios.forEach(r => r.addEventListener('change', () => handleUpdate(true)));
// Mode Switch -> Change Form & Rebuild
modeSelector.addEventListener('change', (e) => { modeSelector.addEventListener('change', (e) => {
const newMode = e.target.value; const newMode = e.target.value;
document.querySelectorAll('.input-form').forEach(f => f.classList.remove('active')); document.querySelectorAll('.input-form').forEach(f => f.classList.remove('active'));
document.getElementById(`form-${newMode}`).classList.add('active'); document.getElementById(`form-${newMode}`).classList.add('active');
const newForm = document.getElementById(`form-${newMode}`);
const firstField = newForm.querySelector('input, textarea');
if (firstField) firstField.focus();
handleUpdate(true); handleUpdate(true);
}); });
// Data Entry -> Debounced Rebuild
document.querySelectorAll('.input-form').forEach(form => { document.querySelectorAll('.input-form').forEach(form => {
form.addEventListener('input', () => handleUpdate(false)); form.addEventListener('input', () => handleUpdate(false));
}); });
// Download Handler // Clear Button
if (clearBtn) {
clearBtn.addEventListener('click', resetApp);
}
downloadBtn.addEventListener('click', () => { downloadBtn.addEventListener('click', () => {
const format = getFormat(); const format = getFormat();
if (format === 'png') { if (format === 'png') {
const img = qrContainer.querySelector('img'); const img = qrContainer.querySelector('img');
if (img && img.src) { if (img && img.src) {
@@ -297,6 +290,21 @@ document.addEventListener('DOMContentLoaded', () => {
} }
}); });
// Initial Render // --- 7. STARTUP SEQUENCE (Browser Restore Friendly) ---
// Check what mode the browser remembered
const initialMode = modeSelector.value;
// Force the correct form to show based on that mode
document.querySelectorAll('.input-form').forEach(f => f.classList.remove('active'));
const initialForm = document.getElementById(`form-${initialMode}`);
if (initialForm) {
initialForm.classList.add('active');
// Auto-focus the restored form
const startField = initialForm.querySelector('input, textarea');
if (startField) startField.focus();
}
// Render whatever data the browser remembered
handleUpdate(true); handleUpdate(true);
}); });