valtech-components 4.0.167 → 4.0.168
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm2022/lib/components/molecules/ticket-card/ticket-card.component.mjs +4 -1
- package/esm2022/lib/components/molecules/ticket-card/types.mjs +1 -1
- package/esm2022/lib/services/ticket-card-image/ticket-card-image.service.mjs +117 -41
- package/esm2022/lib/services/ticket-card-image/types.mjs +1 -1
- package/esm2022/lib/version.mjs +2 -2
- package/fesm2022/valtech-components.mjs +120 -41
- package/fesm2022/valtech-components.mjs.map +1 -1
- package/lib/components/molecules/ticket-card/types.d.ts +7 -1
- package/lib/services/ticket-card-image/ticket-card-image.service.d.ts +3 -0
- package/lib/services/ticket-card-image/types.d.ts +6 -0
- package/lib/version.d.ts +1 -1
- package/package.json +1 -1
|
@@ -56,7 +56,7 @@ import { BrowserMultiFormatReader } from '@zxing/browser';
|
|
|
56
56
|
* Current version of valtech-components.
|
|
57
57
|
* This is automatically updated during the publish process.
|
|
58
58
|
*/
|
|
59
|
-
const VERSION = '4.0.
|
|
59
|
+
const VERSION = '4.0.168';
|
|
60
60
|
|
|
61
61
|
// Control de estado de refresco (singleton a nivel de módulo)
|
|
62
62
|
let isRefreshing = false;
|
|
@@ -61516,14 +61516,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
61516
61516
|
// Use LocaleService for language management instead
|
|
61517
61517
|
|
|
61518
61518
|
const CANVAS_WIDTH = 560;
|
|
61519
|
-
const HEADER_H =
|
|
61520
|
-
const QR_ZONE_H =
|
|
61521
|
-
const
|
|
61522
|
-
const
|
|
61523
|
-
const
|
|
61519
|
+
const HEADER_H = 210;
|
|
61520
|
+
const QR_ZONE_H = 340;
|
|
61521
|
+
const PERF_H = 28;
|
|
61522
|
+
const FOOTER_H = 220;
|
|
61523
|
+
const CANVAS_HEIGHT = HEADER_H + QR_ZONE_H + PERF_H + FOOTER_H;
|
|
61524
|
+
const QR_SIZE = 260;
|
|
61524
61525
|
const DEFAULT_BRAND_COLOR = '#9b1fde';
|
|
61525
61526
|
const DEFAULT_BRAND_COLOR_TO = '#c93075';
|
|
61526
|
-
const FONT_STACK = 'Khula, Arial, sans-serif';
|
|
61527
|
+
const FONT_STACK = '"Plus Jakarta Sans", Khula, Arial, sans-serif';
|
|
61527
61528
|
class TicketCardImageService {
|
|
61528
61529
|
async generate(config) {
|
|
61529
61530
|
const width = config.width ?? CANVAS_WIDTH;
|
|
@@ -61541,6 +61542,7 @@ class TicketCardImageService {
|
|
|
61541
61542
|
}
|
|
61542
61543
|
await this.drawHeader(ctx, config, width);
|
|
61543
61544
|
await this.drawQrZone(ctx, config, width);
|
|
61545
|
+
this.drawPerforated(ctx, config, width);
|
|
61544
61546
|
this.drawFooter(ctx, config, width);
|
|
61545
61547
|
if (canvas instanceof OffscreenCanvas) {
|
|
61546
61548
|
return canvas.convertToBlob({ type: 'image/png' });
|
|
@@ -61594,31 +61596,41 @@ class TicketCardImageService {
|
|
|
61594
61596
|
grad.addColorStop(1, colorTo);
|
|
61595
61597
|
ctx.fillStyle = grad;
|
|
61596
61598
|
ctx.fillRect(0, 0, width, HEADER_H);
|
|
61597
|
-
const logoH =
|
|
61598
|
-
const
|
|
61599
|
-
let
|
|
61599
|
+
const logoH = 48;
|
|
61600
|
+
const logoPadTop = 28;
|
|
61601
|
+
let cursorY = logoPadTop;
|
|
61600
61602
|
if (config.logoUrl) {
|
|
61601
61603
|
try {
|
|
61602
61604
|
const logoImg = await this.loadImage(config.logoUrl);
|
|
61603
61605
|
const ratio = logoImg.width / logoImg.height;
|
|
61604
61606
|
const logoW = logoH * ratio;
|
|
61605
|
-
const logoX =
|
|
61606
|
-
|
|
61607
|
-
|
|
61608
|
-
if (config.brandName) {
|
|
61609
|
-
textY = logoY + logoH + 14;
|
|
61610
|
-
}
|
|
61607
|
+
const logoX = (width - logoW) / 2;
|
|
61608
|
+
ctx.drawImage(logoImg, logoX, cursorY, logoW, logoH);
|
|
61609
|
+
cursorY += logoH + 14;
|
|
61611
61610
|
}
|
|
61612
61611
|
catch {
|
|
61613
|
-
// logo failed —
|
|
61612
|
+
// logo failed — continue without it
|
|
61614
61613
|
}
|
|
61615
61614
|
}
|
|
61616
61615
|
if (config.brandName) {
|
|
61617
61616
|
ctx.fillStyle = '#ffffff';
|
|
61618
|
-
ctx.font = `bold 1.
|
|
61617
|
+
ctx.font = `bold 1.625rem ${FONT_STACK}`;
|
|
61618
|
+
ctx.textAlign = 'center';
|
|
61619
|
+
ctx.textBaseline = 'top';
|
|
61620
|
+
ctx.fillText(config.brandName, width / 2, cursorY, width - 48);
|
|
61621
|
+
cursorY += 32;
|
|
61622
|
+
}
|
|
61623
|
+
const metaParts = [];
|
|
61624
|
+
if (config.eventDate)
|
|
61625
|
+
metaParts.push(config.eventDate);
|
|
61626
|
+
if (config.eventLocation)
|
|
61627
|
+
metaParts.push(config.eventLocation);
|
|
61628
|
+
if (metaParts.length > 0) {
|
|
61629
|
+
ctx.fillStyle = 'rgba(255,255,255,0.78)';
|
|
61630
|
+
ctx.font = `0.875rem ${FONT_STACK}`;
|
|
61619
61631
|
ctx.textAlign = 'center';
|
|
61620
|
-
ctx.textBaseline = '
|
|
61621
|
-
ctx.fillText(
|
|
61632
|
+
ctx.textBaseline = 'top';
|
|
61633
|
+
ctx.fillText(metaParts.join(' · '), width / 2, cursorY, width - 48);
|
|
61622
61634
|
}
|
|
61623
61635
|
}
|
|
61624
61636
|
async drawQrZone(ctx, config, width) {
|
|
@@ -61626,7 +61638,7 @@ class TicketCardImageService {
|
|
|
61626
61638
|
ctx.fillStyle = '#ffffff';
|
|
61627
61639
|
ctx.fillRect(0, zoneTop, width, QR_ZONE_H);
|
|
61628
61640
|
const qrX = (width - QR_SIZE) / 2;
|
|
61629
|
-
const qrY = zoneTop + (QR_ZONE_H - QR_SIZE -
|
|
61641
|
+
const qrY = zoneTop + (QR_ZONE_H - QR_SIZE - 40) / 2;
|
|
61630
61642
|
try {
|
|
61631
61643
|
const qrImg = await this.loadImage(config.qrDataUrl);
|
|
61632
61644
|
ctx.drawImage(qrImg, qrX, qrY, QR_SIZE, QR_SIZE);
|
|
@@ -61635,42 +61647,106 @@ class TicketCardImageService {
|
|
|
61635
61647
|
ctx.strokeStyle = '#e0e0e0';
|
|
61636
61648
|
ctx.strokeRect(qrX, qrY, QR_SIZE, QR_SIZE);
|
|
61637
61649
|
}
|
|
61638
|
-
|
|
61639
|
-
ctx.
|
|
61650
|
+
const hint = config.scanHint ?? 'Muestra este QR en la puerta para canjear tu entrada.';
|
|
61651
|
+
ctx.fillStyle = '#6b6675';
|
|
61652
|
+
ctx.font = `0.875rem ${FONT_STACK}`;
|
|
61640
61653
|
ctx.textAlign = 'center';
|
|
61641
61654
|
ctx.textBaseline = 'top';
|
|
61642
|
-
|
|
61655
|
+
const hintY = qrY + QR_SIZE + 14;
|
|
61656
|
+
this.fillWrappedText(ctx, hint, width / 2, hintY, width - 80, 22);
|
|
61657
|
+
}
|
|
61658
|
+
drawPerforated(ctx, config, width) {
|
|
61659
|
+
const y = HEADER_H + QR_ZONE_H;
|
|
61660
|
+
const notchR = PERF_H / 2;
|
|
61661
|
+
const colorFrom = config.brandColor ?? DEFAULT_BRAND_COLOR;
|
|
61662
|
+
ctx.fillStyle = '#ffffff';
|
|
61663
|
+
ctx.fillRect(0, y, width, PERF_H);
|
|
61664
|
+
// Left notch (semicircle cut from edge)
|
|
61665
|
+
ctx.fillStyle = '#f8f8f8';
|
|
61666
|
+
ctx.beginPath();
|
|
61667
|
+
ctx.arc(-1, y + notchR, notchR + 1, -Math.PI / 2, Math.PI / 2);
|
|
61668
|
+
ctx.fill();
|
|
61669
|
+
// Right notch
|
|
61670
|
+
ctx.beginPath();
|
|
61671
|
+
ctx.arc(width + 1, y + notchR, notchR + 1, Math.PI / 2, -Math.PI / 2);
|
|
61672
|
+
ctx.fill();
|
|
61673
|
+
// Dashed line in middle
|
|
61674
|
+
ctx.setLineDash([8, 6]);
|
|
61675
|
+
ctx.strokeStyle = colorFrom + '55';
|
|
61676
|
+
ctx.lineWidth = 1.5;
|
|
61677
|
+
ctx.beginPath();
|
|
61678
|
+
ctx.moveTo(notchR * 2 + 8, y + notchR);
|
|
61679
|
+
ctx.lineTo(width - notchR * 2 - 8, y + notchR);
|
|
61680
|
+
ctx.stroke();
|
|
61681
|
+
ctx.setLineDash([]);
|
|
61643
61682
|
}
|
|
61644
61683
|
drawFooter(ctx, config, width) {
|
|
61645
|
-
const footerTop = HEADER_H + QR_ZONE_H;
|
|
61684
|
+
const footerTop = HEADER_H + QR_ZONE_H + PERF_H;
|
|
61685
|
+
const colorFrom = config.brandColor ?? DEFAULT_BRAND_COLOR;
|
|
61646
61686
|
ctx.fillStyle = '#ffffff';
|
|
61647
61687
|
ctx.fillRect(0, footerTop, width, FOOTER_H);
|
|
61648
|
-
|
|
61649
|
-
ctx.fillStyle = colorFrom;
|
|
61650
|
-
ctx.fillRect(0, footerTop, width, 3);
|
|
61651
|
-
let contentY = footerTop + 40;
|
|
61688
|
+
let contentY = footerTop + 32;
|
|
61652
61689
|
if (config.folio !== undefined && config.folio !== null && config.folio !== '') {
|
|
61653
|
-
const label = config.folioLabel ?? '
|
|
61654
|
-
|
|
61655
|
-
ctx.fillStyle =
|
|
61656
|
-
ctx.font = `
|
|
61690
|
+
const label = (config.folioLabel ?? 'Cartón').toUpperCase();
|
|
61691
|
+
// Label small — brand color, letter-spaced
|
|
61692
|
+
ctx.fillStyle = colorFrom;
|
|
61693
|
+
ctx.font = `600 0.8125rem ${FONT_STACK}`;
|
|
61657
61694
|
ctx.textAlign = 'center';
|
|
61658
61695
|
ctx.textBaseline = 'top';
|
|
61659
|
-
|
|
61660
|
-
contentY +=
|
|
61696
|
+
this.fillLetterSpaced(ctx, label, width / 2, contentY, 3);
|
|
61697
|
+
contentY += 24;
|
|
61698
|
+
// Folio number — large, bold, brand color
|
|
61699
|
+
ctx.fillStyle = colorFrom;
|
|
61700
|
+
ctx.font = `800 3.5rem ${FONT_STACK}`;
|
|
61701
|
+
ctx.textAlign = 'center';
|
|
61702
|
+
ctx.textBaseline = 'top';
|
|
61703
|
+
ctx.fillText(`#${config.folio}`, width / 2, contentY, width - 40);
|
|
61704
|
+
contentY += 68;
|
|
61661
61705
|
}
|
|
61662
61706
|
if (config.buyerName) {
|
|
61663
|
-
ctx.fillStyle = '#
|
|
61664
|
-
ctx.font = `
|
|
61707
|
+
ctx.fillStyle = '#6b6675';
|
|
61708
|
+
ctx.font = `0.9375rem ${FONT_STACK}`;
|
|
61665
61709
|
ctx.textAlign = 'center';
|
|
61666
61710
|
ctx.textBaseline = 'top';
|
|
61667
61711
|
ctx.fillText(config.buyerName, width / 2, contentY, width - 40);
|
|
61668
61712
|
}
|
|
61669
|
-
|
|
61670
|
-
ctx.
|
|
61671
|
-
ctx.
|
|
61713
|
+
// Copyright
|
|
61714
|
+
ctx.fillStyle = '#b0a8bc';
|
|
61715
|
+
ctx.font = `0.6875rem ${FONT_STACK}`;
|
|
61716
|
+
ctx.textAlign = 'center';
|
|
61672
61717
|
ctx.textBaseline = 'bottom';
|
|
61673
|
-
ctx.fillText('Valtech', width
|
|
61718
|
+
ctx.fillText('© 2026 Valtech. Todos los derechos reservados.', width / 2, footerTop + FOOTER_H - 14);
|
|
61719
|
+
}
|
|
61720
|
+
fillWrappedText(ctx, text, x, y, maxWidth, lineHeight) {
|
|
61721
|
+
const words = text.split(' ');
|
|
61722
|
+
let line = '';
|
|
61723
|
+
let curY = y;
|
|
61724
|
+
for (const word of words) {
|
|
61725
|
+
const test = line ? `${line} ${word}` : word;
|
|
61726
|
+
if (ctx.measureText(test).width > maxWidth && line) {
|
|
61727
|
+
ctx.fillText(line, x, curY);
|
|
61728
|
+
line = word;
|
|
61729
|
+
curY += lineHeight;
|
|
61730
|
+
}
|
|
61731
|
+
else {
|
|
61732
|
+
line = test;
|
|
61733
|
+
}
|
|
61734
|
+
}
|
|
61735
|
+
if (line)
|
|
61736
|
+
ctx.fillText(line, x, curY);
|
|
61737
|
+
}
|
|
61738
|
+
fillLetterSpaced(ctx, text, cx, y, spacing) {
|
|
61739
|
+
// Measure total width with spacing and center
|
|
61740
|
+
const chars = text.split('');
|
|
61741
|
+
const charWidths = chars.map(c => ctx.measureText(c).width);
|
|
61742
|
+
const totalW = charWidths.reduce((s, w) => s + w, 0) + spacing * (chars.length - 1);
|
|
61743
|
+
let x = cx - totalW / 2;
|
|
61744
|
+
ctx.textAlign = 'left';
|
|
61745
|
+
for (let i = 0; i < chars.length; i++) {
|
|
61746
|
+
ctx.fillText(chars[i], x, y);
|
|
61747
|
+
x += charWidths[i] + spacing;
|
|
61748
|
+
}
|
|
61749
|
+
ctx.textAlign = 'center';
|
|
61674
61750
|
}
|
|
61675
61751
|
loadImage(src) {
|
|
61676
61752
|
return new Promise((resolve, reject) => {
|
|
@@ -70432,6 +70508,9 @@ class TicketCardComponent {
|
|
|
70432
70508
|
folio: p.folio,
|
|
70433
70509
|
folioLabel: p.shareFolioLabel,
|
|
70434
70510
|
buyerName: p.buyerName,
|
|
70511
|
+
eventDate: p.shareEventDate,
|
|
70512
|
+
eventLocation: p.shareEventLocation,
|
|
70513
|
+
scanHint: p.shareScanHint,
|
|
70435
70514
|
};
|
|
70436
70515
|
}
|
|
70437
70516
|
async copyToClipboard(config, p) {
|