Implemented the print.json translation file. Added a new print dialog. Moved /print function to a post request. Updated translation.js debug output. Implemented translator within print.js. Fixed typos. Removed unused utils from email and print components render function

This commit is contained in:
Glenn de Haan
2024-10-13 10:37:26 +02:00
parent d5c19d021d
commit c3b36b55a3
7 changed files with 150 additions and 38 deletions

15
locales/en/print.json Normal file
View File

@@ -0,0 +1,15 @@
{
"title": "WiFi Voucher Code",
"connect": "Connect to",
"password": "Password",
"or": "or",
"scan": "Scan to connect",
"details": "Voucher Details",
"type": "Type",
"multiUse": "Multi-use",
"singleUse": "Single-use",
"duration": "Duration",
"dataLimit": "Data Limit",
"downloadLimit": "Download Limit",
"uploadLimit": "Upload Limit"
}

View File

@@ -11,6 +11,7 @@ const PrinterTypes = require('node-thermal-printer').types;
const variables = require('./variables');
const log = require('./log');
const qr = require('./qr');
const translation = require('./translation');
/**
* Import own utils
@@ -27,10 +28,14 @@ module.exports = {
* Generates a voucher as a PDF
*
* @param voucher
* @param language
* @return {Promise<unknown>}
*/
pdf: (voucher) => {
pdf: (voucher, language) => {
return new Promise(async (resolve) => {
// Create new translator
const t = translation('print', language);
const doc = new PDFDocument({
bufferPages: true,
size: [226.77165354330398, size(voucher)],
@@ -54,7 +59,7 @@ module.exports = {
doc.font('Helvetica-Bold')
.fontSize(20)
.text(`WiFi Voucher Code`, {
.text(`${t('title')}`, {
align: 'center'
});
doc.font('Helvetica-Bold')
@@ -68,7 +73,7 @@ module.exports = {
if(variables.unifiSsid !== '') {
doc.font('Helvetica')
.fontSize(10)
.text(`Connect to: `, {
.text(`${t('connect')}: `, {
continued: true
});
doc.font('Helvetica-Bold')
@@ -83,7 +88,7 @@ module.exports = {
.text(`,`);
doc.font('Helvetica')
.fontSize(10)
.text(`Password: `, {
.text(`${t('password')}: `, {
continued: true
});
doc.font('Helvetica-Bold')
@@ -93,16 +98,16 @@ module.exports = {
});
doc.font('Helvetica')
.fontSize(10)
.text(` or,`);
.text(` ${t('or')},`);
} else {
doc.font('Helvetica')
.fontSize(10)
.text(` or,`);
.text(` ${t('or')},`);
}
doc.font('Helvetica')
.fontSize(10)
.text(`Scan to connect:`);
.text(`${t('scan')}:`);
doc.image(await qr(), 75, variables.unifiSsidPassword !== '' ? 215 : 205, {fit: [75, 75], align: 'center', valign: 'center'});
doc.moveDown(6);
@@ -112,7 +117,7 @@ module.exports = {
doc.font('Helvetica-Bold')
.fontSize(12)
.text(`Voucher Details`);
.text(`${t('details')}`);
doc.font('Helvetica-Bold')
.fontSize(10)
@@ -120,16 +125,16 @@ module.exports = {
doc.font('Helvetica-Bold')
.fontSize(10)
.text(`Type: `, {
.text(`${t('type')}: `, {
continued: true
});
doc.font('Helvetica')
.fontSize(10)
.text(voucher.quota === 0 ? 'Multi-use' : 'Single-use');
.text(voucher.quota === 0 ? t('multiUse') : t('singleUse'));
doc.font('Helvetica-Bold')
.fontSize(10)
.text(`Duration: `, {
.text(`${t('duration')}: `, {
continued: true
});
doc.font('Helvetica')
@@ -139,7 +144,7 @@ module.exports = {
if(voucher.qos_usage_quota) {
doc.font('Helvetica-Bold')
.fontSize(10)
.text(`Data Limit: `, {
.text(`${t('dataLimit')}: `, {
continued: true
});
doc.font('Helvetica')
@@ -150,7 +155,7 @@ module.exports = {
if(voucher.qos_rate_max_down) {
doc.font('Helvetica-Bold')
.fontSize(10)
.text(`Download Limit: `, {
.text(`${t('downloadLimit')}: `, {
continued: true
});
doc.font('Helvetica')
@@ -161,7 +166,7 @@ module.exports = {
if(voucher.qos_rate_max_up) {
doc.font('Helvetica-Bold')
.fontSize(10)
.text(`Upload Limit: `, {
.text(`${t('uploadLimit')}: `, {
continued: true
});
doc.font('Helvetica')
@@ -177,10 +182,14 @@ module.exports = {
* Sends a print job to an ESC/POS compatible network printer
*
* @param voucher
* @param language
* @return {Promise<unknown>}
*/
escpos: (voucher) => {
escpos: (voucher, language) => {
return new Promise(async (resolve, reject) => {
// Create new translator
const t = translation('print', language);
const printer = new ThermalPrinter({
type: PrinterTypes.EPSON,
interface: `tcp://${variables.printerIp}`
@@ -202,7 +211,7 @@ module.exports = {
printer.alignCenter();
printer.newLine();
printer.setTextSize(2, 2);
printer.println('WiFi Voucher Code');
printer.println(`${t('title')}`);
printer.setTextSize(1, 1);
printer.println(`${voucher.code.slice(0, 5)}-${voucher.code.slice(5)}`);
printer.setTextNormal();
@@ -213,7 +222,7 @@ module.exports = {
printer.newLine();
printer.alignLeft();
printer.print('Connect to: ');
printer.print(`${t('connect')}: `);
printer.setTypeFontB();
printer.setTextSize(1, 1);
printer.print(variables.unifiSsid);
@@ -221,18 +230,18 @@ module.exports = {
if(variables.unifiSsidPassword) {
printer.print(',');
printer.newLine();
printer.print('Password: ');
printer.print(`${t('password')}: `);
printer.setTypeFontB();
printer.setTextSize(1, 1);
printer.print(variables.unifiSsidPassword);
printer.setTextNormal();
printer.print(' or,');
printer.print(` ${t('or')},`);
printer.newLine();
} else {
printer.print(' or,');
printer.print(` ${t('or')},`);
printer.newLine();
}
printer.println('Scan to connect:');
printer.println(`${t('scan')}:`);
printer.alignCenter();
await printer.printImageBuffer(await qr(true));
}
@@ -243,20 +252,20 @@ module.exports = {
printer.alignLeft();
printer.setTypeFontB();
printer.setTextSize(1, 1);
printer.println('Voucher Details');
printer.println(`${t('details')}`);
printer.setTextNormal();
printer.drawLine();
printer.setTextDoubleHeight();
printer.invert(true);
printer.print('Type:');
printer.print(`${t('type')}:`);
printer.invert(false);
printer.print(voucher.quota === 0 ? ' Multi-use' : ' Single-use');
printer.print(voucher.quota === 0 ? ` ${t('multiUse')}` : ` ${t('singleUse')}`);
printer.newLine();
printer.setTextDoubleHeight();
printer.invert(true);
printer.print('Duration:');
printer.print(`${t('duration')}:`);
printer.invert(false);
printer.print(` ${time(voucher.duration)}`);
printer.newLine();
@@ -264,7 +273,7 @@ module.exports = {
if(voucher.qos_usage_quota) {
printer.setTextDoubleHeight();
printer.invert(true);
printer.print('Data Limit:');
printer.print(`${t('dataLimit')}:`);
printer.invert(false);
printer.print(` ${bytes(voucher.qos_usage_quota, 2)}`);
printer.newLine();
@@ -273,7 +282,7 @@ module.exports = {
if(voucher.qos_rate_max_down) {
printer.setTextDoubleHeight();
printer.invert(true);
printer.print('Download Limit:');
printer.print(`${t('downloadLimit')}:`);
printer.invert(false);
printer.print(` ${bytes(voucher.qos_rate_max_down, 1, true)}`);
printer.newLine();
@@ -282,7 +291,7 @@ module.exports = {
if(voucher.qos_rate_max_up) {
printer.setTextDoubleHeight();
printer.invert(true);
printer.print('Upload Limit:');
printer.print(`${t('uploadLimit')}:`);
printer.invert(false);
printer.print(` ${bytes(voucher.qos_rate_max_up, 1, true)}`);
printer.newLine();

View File

@@ -43,6 +43,6 @@ module.exports = (module, language = 'en', fallback = 'en') => {
}
// Check if debugging is enabled. If enabled only return key
return variables.translationDebug ? `%${key}%` : translations[key];
return variables.translationDebug ? `t('${key}')` : translations[key];
};
};

View File

@@ -257,9 +257,33 @@ if(variables.serviceWeb) {
return e._id === req.params.id;
});
if(voucher) {
res.render('components/print', {
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : '',
languages,
voucher,
updated: cache.updated
});
} else {
res.status(404);
res.render('404', {
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''
});
}
});
app.post('/voucher/:id/print', [authorization.web], async (req, res) => {
if(variables.printerType === '') {
res.status(501).send();
return;
}
const voucher = cache.vouchers.find((e) => {
return e._id === req.params.id;
});
if(voucher) {
if(variables.printerType === 'pdf') {
const buffers = await print.pdf(voucher);
const buffers = await print.pdf(voucher, req.body.language);
const pdfData = Buffer.concat(buffers);
res.writeHead(200, {
'Content-Length': Buffer.byteLength(pdfData),
@@ -269,7 +293,7 @@ if(variables.serviceWeb) {
}
if(variables.printerType === 'escpos') {
const printResult = await print.escpos(voucher).catch((e) => {
const printResult = await print.escpos(voucher, req.body.language).catch((e) => {
res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
});
@@ -297,8 +321,6 @@ if(variables.serviceWeb) {
if(voucher) {
res.render('components/email', {
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : '',
timeConvert: time,
bytesConvert: bytes,
languages,
voucher,
updated: cache.updated

View File

@@ -5,7 +5,7 @@
<div class="absolute inset-0 overflow-hidden">
<div class="pointer-events-none fixed inset-y-0 right-0 flex max-w-full pl-10 sm:pl-16">
<div class="pointer-events-auto w-screen max-w-md">
<form id="email-forum" class="flex h-full flex-col divide-y divide-black/5 dark:divide-white/5 bg-white dark:bg-gray-900 shadow-xl" action="<%= baseUrl %>/voucher/<%= voucher._id %>/email" method="post" enctype="multipart/form-data">
<form id="email-form" class="flex h-full flex-col divide-y divide-black/5 dark:divide-white/5 bg-white dark:bg-gray-900 shadow-xl" action="<%= baseUrl %>/voucher/<%= voucher._id %>/email" method="post" enctype="multipart/form-data">
<div class="h-0 flex-1 overflow-y-auto">
<div class="bg-sky-700 px-4 py-6 sm:px-6">
<div class="flex items-center justify-between">

View File

@@ -0,0 +1,44 @@
<div class="relative z-40" role="dialog" aria-modal="true">
<div id="overlay" class="fixed inset-0 bg-gray-500 bg-opacity-75"></div>
<div class="fixed overflow-hidden">
<div class="absolute inset-0 overflow-hidden">
<div class="pointer-events-none fixed inset-y-0 right-0 flex max-w-full pl-10 sm:pl-16">
<div class="pointer-events-auto w-screen max-w-md">
<form id="print-form" class="flex h-full flex-col divide-y divide-black/5 dark:divide-white/5 bg-white dark:bg-gray-900 shadow-xl" action="<%= baseUrl %>/voucher/<%= voucher._id %>/print" method="post" enctype="multipart/form-data">
<div class="h-0 flex-1 overflow-y-auto">
<div class="bg-sky-700 px-4 py-6 sm:px-6">
<div class="flex items-center justify-between">
<h2 class="text-base font-semibold leading-6 text-white" id="slide-over-title">Print Voucher</h2>
</div>
<div class="mt-1">
<p class="text-sm text-sky-100">Get started by filling in the information below to print the selected voucher.</p>
</div>
</div>
<div class="flex flex-1 flex-col justify-between">
<div class="divide-y divide-black/5 dark:divide-white/5 px-4 sm:px-6">
<div class="space-y-6 pb-5 pt-6">
<div>
<label for="language" class="block text-sm font-medium leading-6 text-gray-900 dark:text-white">Language</label>
<div class="mt-2">
<select id="language" name="language" class="mt-2 block w-full rounded-md border-0 py-1.5 pl-3 pr-10 text-gray-900 dark:text-white dark:bg-white/5 ring-1 ring-inset ring-gray-300 dark:ring-white/10 focus:ring-2 focus:ring-sky-600 sm:text-sm sm:leading-6 [&_*]:text-black">
<% Object.keys(languages).forEach((language) => { %>
<option value="<%= language %>"><%= languages[language] %> (<%= language %>)</option>
<% }); %>
</select>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="flex flex-shrink-0 justify-end px-4 py-4">
<button id="close" type="button" class="rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50">Cancel</button>
<button type="submit" class="ml-4 inline-flex justify-center rounded-md bg-sky-700 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-sky-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sky-700">Print</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>

View File

@@ -195,13 +195,13 @@
<path d="M22.5 6.908V6.75a3 3 0 0 0-3-3h-15a3 3 0 0 0-3 3v.158l9.714 5.978a1.5 1.5 0 0 0 1.572 0L22.5 6.908Z" />
</svg>
</button>
<a href="<%= baseUrl %>/voucher/<%= voucher._id %>/print" type="button" class="relative rounded-full p-1 text-gray-500 dark:text-gray-400 hover:text-black dark:hover:text-white<%= !printer_enabled ? ' hidden' : '' %>">
<button type="button" data-id="<%= voucher._id %>" class="voucher-print relative rounded-full p-1 text-gray-500 dark:text-gray-400 hover:text-black dark:hover:text-white<%= !printer_enabled ? ' hidden' : '' %>">
<span class="absolute -inset-1.5"></span>
<span class="sr-only">Print Voucher Code</span>
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M7.875 1.5C6.839 1.5 6 2.34 6 3.375v2.99c-.426.053-.851.11-1.274.174-1.454.218-2.476 1.483-2.476 2.917v6.294a3 3 0 0 0 3 3h.27l-.155 1.705A1.875 1.875 0 0 0 7.232 22.5h9.536a1.875 1.875 0 0 0 1.867-2.045l-.155-1.705h.27a3 3 0 0 0 3-3V9.456c0-1.434-1.022-2.7-2.476-2.917A48.716 48.716 0 0 0 18 6.366V3.375c0-1.036-.84-1.875-1.875-1.875h-8.25ZM16.5 6.205v-2.83A.375.375 0 0 0 16.125 3h-8.25a.375.375 0 0 0-.375.375v2.83a49.353 49.353 0 0 1 9 0Zm-.217 8.265c.178.018.317.16.333.337l.526 5.784a.375.375 0 0 1-.374.409H7.232a.375.375 0 0 1-.374-.409l.526-5.784a.373.373 0 0 1 .333-.337 41.741 41.741 0 0 1 8.566 0Zm.967-3.97a.75.75 0 0 1 .75-.75h.008a.75.75 0 0 1 .75.75v.008a.75.75 0 0 1-.75.75H18a.75.75 0 0 1-.75-.75V10.5ZM15 9.75a.75.75 0 0 0-.75.75v.008c0 .414.336.75.75.75h.008a.75.75 0 0 0 .75-.75V10.5a.75.75 0 0 0-.75-.75H15Z" clip-rule="evenodd" />
</svg>
</a>
</button>
<a href="<%= baseUrl %>/voucher/<%= voucher._id %>/remove" type="button" class="remove-button relative rounded-full p-1 text-red-500 dark:text-red-400 hover:text-black dark:hover:text-white">
<span class="absolute -inset-1.5"></span>
<span class="sr-only">Remove Voucher Code</span>
@@ -217,6 +217,7 @@
<div id="detail-dialog"></div>
<div id="email-dialog"></div>
<div id="print-dialog"></div>
<div id="create-dialog" class="hidden relative z-40" role="dialog" aria-modal="true">
<div id="create-dialog-overlay" class="fixed inset-0 bg-gray-500 bg-opacity-75"></div>
@@ -389,6 +390,7 @@
const createDialogOverlay = document.querySelector('#create-dialog-overlay');
const detailDialog = document.querySelector('#detail-dialog');
const emailDialog = document.querySelector('#email-dialog');
const printDialog = document.querySelector('#print-dialog');
const createForm = document.querySelector('#voucher-form');
const voucherTypeField = document.querySelector('#voucher-type');
const customVoucherFields = document.querySelectorAll('.custom-voucher-field');
@@ -405,6 +407,7 @@
const copyNotification = document.querySelector('#copy-notification');
const vouchers = document.querySelectorAll('.voucher');
const vouchersEmail = document.querySelectorAll('.voucher-email');
const vouchersPrint = document.querySelectorAll('.voucher-print');
const filterSortForm = document.querySelector('#filter-sort-form');
const statusFilter = document.querySelector('#status');
const quotaFilter = document.querySelector('#quota');
@@ -417,12 +420,18 @@
};
const clearEmailDialog = () => {
document.querySelector('#email-forum').removeEventListener('submit', emailSpinner);
document.querySelector('#email-form').removeEventListener('submit', emailSpinner);
document.querySelector('#close').removeEventListener('click', clearEmailDialog);
document.querySelector('#overlay').removeEventListener('click', clearEmailDialog);
emailDialog.innerHTML = '';
};
const clearPrintDialog = () => {
document.querySelector('#close').removeEventListener('click', clearPrintDialog);
document.querySelector('#overlay').removeEventListener('click', clearPrintDialog);
printDialog.innerHTML = '';
};
const emailSpinner = () => {
spinnerEmail.style.display = '';
};
@@ -498,7 +507,20 @@
emailDialog.innerHTML = await htmlRes.text();
document.querySelector('#close').addEventListener('click', clearEmailDialog);
document.querySelector('#overlay').addEventListener('click', clearEmailDialog);
document.querySelector('#email-forum').addEventListener('submit', emailSpinner);
document.querySelector('#email-form').addEventListener('submit', emailSpinner);
} else {
window.location.reload();
}
});
});
vouchersPrint.forEach((el) => {
el.addEventListener('click', async () => {
const htmlRes = await fetch(`<%= baseUrl %>/voucher/${el.dataset.id}/print`);
if(htmlRes.status === 200 && !htmlRes.redirected) {
printDialog.innerHTML = await htmlRes.text();
document.querySelector('#close').addEventListener('click', clearPrintDialog);
document.querySelector('#overlay').addEventListener('click', clearPrintDialog);
} else {
window.location.reload();
}