Add reset button
This commit is contained in:
@@ -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
102
script.js
@@ -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);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user