added copy button and error messages to code generator

This commit is contained in:
ph1r3754r73r
2026-06-24 14:12:44 -07:00
parent 5abc5eb7b4
commit 5096270954
5 changed files with 198 additions and 10 deletions
+1
View File
@@ -508,6 +508,7 @@
<script src="js/utils/openrouterModels.js"></script>
<script src="js/utils/routing.js"></script>
<script src="js/utils/emoji.js"></script>
<script src="js/utils/ean13.js"></script>
<!-- Core modules (feature libraries) -->
<script src="js/core/steganography.js"></script>
+74 -6
View File
@@ -52,7 +52,10 @@ class CodesTool extends Tool {
}
if (this.codesFormat === 'ean13') {
var digits = text.replace(/\D/g, '');
if (digits.length !== 12 && digits.length !== 13) {
if (!digits.length) {
return 'EAN-13 requires digits only (12 or 13 digits). Letters and symbols are not valid.';
}
if (digits.length < 12 || digits.length > 13) {
return 'EAN-13 requires 12 or 13 digits (check digit is calculated automatically for 12).';
}
}
@@ -64,23 +67,34 @@ class CodesTool extends Tool {
codesClearOutput: function() {
this.codesOutputUrl = '';
this.codesOutputSvg = '';
},
codesResetGenerate: function() {
this.codesClearOutput();
this.codesError = '';
},
codesGenerate: function() {
var validationError = this.codesValidateGenerateInput();
if (validationError) {
this.codesError = validationError;
this.codesClearOutput();
this.codesError = validationError;
return;
}
this.codesError = '';
this.codesOutputUrl = '';
this.codesOutputSvg = '';
this.codesClearOutput();
var text = String(this.codesInput || '').trim();
if (this.codesFormat === 'ean13') {
text = text.replace(/\D/g, '');
if (typeof Ean13Utils === 'undefined' || typeof Ean13Utils.normalize !== 'function') {
this.codesError = 'EAN-13 helpers not loaded. Rebuild the app (npm run build).';
return;
}
var ean13 = Ean13Utils.normalize(text);
if (!ean13.ok) {
this.codesError = ean13.error;
return;
}
text = ean13.value;
}
if (this.codesFormat === 'code39') {
text = text.toUpperCase();
@@ -137,6 +151,60 @@ class CodesTool extends Tool {
this.codesError = (err && err.message) || 'Failed to generate barcode.';
}
},
codesRasterizeToPng: function(imageUrl) {
return new Promise(function(resolve, reject) {
var img = new Image();
img.onload = function() {
var canvas = document.createElement('canvas');
canvas.width = img.naturalWidth || img.width;
canvas.height = img.naturalHeight || img.height;
var ctx = canvas.getContext('2d');
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0);
resolve(canvas.toDataURL('image/png'));
};
img.onerror = function() {
reject(new Error('Failed to render barcode image.'));
};
img.src = imageUrl;
});
},
codesCopyImage: function() {
var self = this;
if (!this.codesOutputUrl) {
return;
}
if (!navigator.clipboard || typeof navigator.clipboard.write !== 'function') {
this.codesError = 'Image copy is not supported in this browser.';
return;
}
var blobPromise;
if (this.codesOutputUrl.indexOf('data:image/png') === 0) {
blobPromise = fetch(this.codesOutputUrl).then(function(response) {
return response.blob();
});
} else {
blobPromise = this.codesRasterizeToPng(this.codesOutputUrl).then(function(pngUrl) {
return fetch(pngUrl).then(function(response) {
return response.blob();
});
});
}
blobPromise.then(function(blob) {
return navigator.clipboard.write([
new ClipboardItem({ 'image/png': blob })
]);
}).then(function() {
if (typeof self.showNotification === 'function') {
self.showNotification('Image copied to clipboard', 'success', 'fas fa-copy');
}
}).catch(function(err) {
self.codesError = (err && err.message) || 'Failed to copy image to clipboard.';
});
},
codesDownload: function() {
if (!this.codesOutputUrl) {
return;
@@ -248,7 +316,7 @@ class CodesTool extends Tool {
}
},
codesFormat: function() {
this.codesClearOutput();
this.codesResetGenerate();
}
};
}
+72
View File
@@ -0,0 +1,72 @@
/**
* EAN-13 normalization helpers for the Codes tool.
*/
(function(global) {
'use strict';
function checksum(base12) {
var sum = 0;
for (var i = 0; i < 12; i++) {
var digit = parseInt(base12.charAt(i), 10);
sum += (i % 2 === 0) ? digit : digit * 3;
}
return String((10 - (sum % 10)) % 10);
}
function upcChecksum(upc12) {
var sum = 0;
for (var i = 0; i < 11; i++) {
var digit = parseInt(upc12.charAt(i), 10);
sum += (i % 2 === 0) ? digit * 3 : digit;
}
return String((10 - (sum % 10)) % 10);
}
function isValidUpcA(digits) {
return digits.length === 12
&& digits.charAt(0) === '0'
&& upcChecksum(digits) === digits.charAt(11);
}
function fromUpcA(upc12) {
var ean12 = '0' + upc12.slice(0, 11);
return ean12 + checksum(ean12);
}
function normalize(input) {
var digits = String(input || '').replace(/\D/g, '');
if (digits.length < 12) {
return {
ok: false,
error: 'EAN-13 requires 12 or 13 digits.'
};
}
if (digits.length > 13) {
return {
ok: false,
error: 'EAN-13 accepts at most 13 digits.'
};
}
if (digits.length === 12 && isValidUpcA(digits)) {
return { ok: true, value: fromUpcA(digits) };
}
var base12 = digits.length === 12 ? digits : digits.slice(0, 12);
return { ok: true, value: base12 + checksum(base12) };
}
var api = {
checksum: checksum,
upcChecksum: upcChecksum,
isValidUpcA: isValidUpcA,
fromUpcA: fromUpcA,
normalize: normalize
};
if (typeof module !== 'undefined' && module.exports) {
module.exports = api;
} else {
global.Ean13Utils = api;
}
})(typeof window !== 'undefined' ? window : globalThis);
+7 -4
View File
@@ -80,29 +80,32 @@
<textarea
v-model="codesInput"
rows="3"
:placeholder="codesFormat === 'ean13' ? '12 or 13 digits, e.g. 5901234123457' : 'Text, URL, or payload…'"
:placeholder="codesFormat === 'ean13' ? '12 or 13 digits, e.g. 590123412345' : 'Text, URL, or payload…'"
spellcheck="false"
></textarea>
</label>
</div>
<div v-if="codesError" class="codes-error" role="alert">{{ codesError }}</div>
<div class="tool-toolbar u-mb-16">
<button type="button" class="transform-button tool-primary-btn" @click="codesGenerate">
<i class="fas fa-qrcode"></i> Generate {{ codesFormatLabel(codesFormat) }}
</button>
</div>
<div v-if="codesError" class="codes-error">{{ codesError }}</div>
<div v-if="codesOutputUrl" class="codes-output-card">
<div class="codes-preview-wrap">
<img :src="codesOutputUrl" :alt="codesFormatLabel(codesFormat)" class="codes-preview-image" />
</div>
<div class="tool-toolbar">
<button type="button" class="action-button copy" @click="codesCopyImage">
<i class="fas fa-copy"></i> Copy
</button>
<button type="button" class="action-button download" @click="codesDownload">
<i class="fas fa-download"></i> Download
</button>
<button type="button" class="btn btn-secondary" @click="codesClearOutput">
<button type="button" class="btn btn-secondary" @click="codesResetGenerate">
<i class="fas fa-eraser"></i> Clear
</button>
</div>
+44
View File
@@ -0,0 +1,44 @@
#!/usr/bin/env node
const assert = require('assert');
const path = require('path');
const Ean13Utils = require(path.join(__dirname, '..', 'js', 'utils', 'ean13.js'));
assert.strictEqual(Ean13Utils.checksum('590123412345'), '7');
assert.strictEqual(Ean13Utils.checksum('400638133393'), '1');
const from12 = Ean13Utils.normalize('590123412345');
assert.strictEqual(from12.ok, true);
assert.strictEqual(from12.value, '5901234123457');
const from13 = Ean13Utils.normalize('5901234123457');
assert.strictEqual(from13.ok, true);
assert.strictEqual(from13.value, '5901234123457');
const wrongCheck = Ean13Utils.normalize('5901234123450');
assert.strictEqual(wrongCheck.ok, true);
assert.strictEqual(wrongCheck.value, '5901234123457');
const withSeparators = Ean13Utils.normalize('590-1234-12345');
assert.strictEqual(withSeparators.ok, true);
assert.strictEqual(withSeparators.value, '5901234123457');
const upcA = Ean13Utils.normalize('036000291452');
assert.strictEqual(upcA.ok, true);
assert.strictEqual(upcA.value, '0036000291452');
const tooShort = Ean13Utils.normalize('12345');
assert.strictEqual(tooShort.ok, false);
const tooLong = Ean13Utils.normalize('12345678901234');
assert.strictEqual(tooLong.ok, false);
const EAN13 = require(path.join(__dirname, '..', 'node_modules', 'jsbarcode', 'bin', 'barcodes', 'EAN_UPC', 'EAN13.js')).default;
const defaults = require(path.join(__dirname, '..', 'node_modules', 'jsbarcode', 'bin', 'options', 'defaults.js')).default;
const normalizedWrong = Ean13Utils.normalize('5901234123450');
const encoder = new EAN13(normalizedWrong.value, Object.assign({}, defaults, { displayValue: true }));
assert.strictEqual(encoder.valid(), true, 'Normalized value should pass JsBarcode validation');
console.log('EAN-13 codes tests passed');