Barcode fix
This commit is contained in:
@@ -62,6 +62,43 @@ const getFrameOptionsForContentType = (contentType: string) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Injects a caption <text> element below a barcode SVG and expands its height/viewBox.
|
||||||
|
// Used so the "scanner app" hint is baked into the downloaded SVG.
|
||||||
|
function addBarcodeCaptionToSvg(svgElement: SVGElement, caption: string): string {
|
||||||
|
const cloned = svgElement.cloneNode(true) as SVGElement;
|
||||||
|
const NS = 'http://www.w3.org/2000/svg';
|
||||||
|
|
||||||
|
const widthAttr = cloned.getAttribute('width');
|
||||||
|
const heightAttr = cloned.getAttribute('height');
|
||||||
|
const width = widthAttr ? parseFloat(widthAttr) : 200;
|
||||||
|
const height = heightAttr ? parseFloat(heightAttr) : 100;
|
||||||
|
const extraHeight = 18;
|
||||||
|
|
||||||
|
cloned.setAttribute('height', String(height + extraHeight));
|
||||||
|
const viewBox = cloned.getAttribute('viewBox');
|
||||||
|
if (viewBox) {
|
||||||
|
const parts = viewBox.split(/\s+/);
|
||||||
|
if (parts.length === 4) {
|
||||||
|
cloned.setAttribute(
|
||||||
|
'viewBox',
|
||||||
|
`${parts[0]} ${parts[1]} ${parts[2]} ${parseFloat(parts[3]) + extraHeight}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = document.createElementNS(NS, 'text');
|
||||||
|
text.setAttribute('x', String(width / 2));
|
||||||
|
text.setAttribute('y', String(height + 12));
|
||||||
|
text.setAttribute('text-anchor', 'middle');
|
||||||
|
text.setAttribute('font-size', '9');
|
||||||
|
text.setAttribute('font-family', 'Arial, Helvetica, sans-serif');
|
||||||
|
text.setAttribute('fill', '#666666');
|
||||||
|
text.textContent = caption;
|
||||||
|
cloned.appendChild(text);
|
||||||
|
|
||||||
|
return new XMLSerializer().serializeToString(cloned);
|
||||||
|
}
|
||||||
|
|
||||||
export default function CreatePage() {
|
export default function CreatePage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -212,7 +249,9 @@ export default function CreatePage() {
|
|||||||
if (frameType === 'none') {
|
if (frameType === 'none') {
|
||||||
const svgElement = qrRef.current.querySelector('svg');
|
const svgElement = qrRef.current.querySelector('svg');
|
||||||
if (svgElement) {
|
if (svgElement) {
|
||||||
const svgData = new XMLSerializer().serializeToString(svgElement);
|
const svgData = contentType === 'BARCODE'
|
||||||
|
? addBarcodeCaptionToSvg(svgElement, 'Scan: iPhone -> Barcode Scanner App | Android -> Google Lens')
|
||||||
|
: new XMLSerializer().serializeToString(svgElement);
|
||||||
const blob = new Blob([svgData], { type: 'image/svg+xml' });
|
const blob = new Blob([svgData], { type: 'image/svg+xml' });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
@@ -651,11 +690,16 @@ export default function CreatePage() {
|
|||||||
<>
|
<>
|
||||||
{isDynamic ? (
|
{isDynamic ? (
|
||||||
<>
|
<>
|
||||||
<div className="rounded-lg bg-blue-50 border border-blue-200 p-3 text-sm text-blue-800">
|
<div className="rounded-lg bg-blue-50 border border-blue-200 p-3 text-sm text-blue-800 space-y-2">
|
||||||
|
<p>
|
||||||
<strong>How dynamic barcodes work:</strong> The barcode encodes a short redirect URL
|
<strong>How dynamic barcodes work:</strong> The barcode encodes a short redirect URL
|
||||||
(e.g. <span className="font-mono text-xs">qrmaster.net/r/…</span>). When scanned with a
|
(e.g. <span className="font-mono text-xs">qrmaster.net/r/…</span>) that you can update anytime.
|
||||||
smartphone camera, it opens the browser and redirects to your destination — which you
|
</p>
|
||||||
can update anytime. Works with smartphone cameras, not POS laser scanners.
|
<p className="rounded border border-amber-300 bg-amber-50 p-2 text-xs text-amber-900">
|
||||||
|
<strong>📱 Scanner tip:</strong> Use a <strong>barcode scanner app</strong> on iPhone
|
||||||
|
(iOS Camera doesn't auto-open links from barcodes). Android Google Lens / Camera works
|
||||||
|
out of the box. Print min. 5 cm wide for reliable scanning.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Input
|
<Input
|
||||||
label="Destination URL"
|
label="Destination URL"
|
||||||
@@ -1038,13 +1082,12 @@ export default function CreatePage() {
|
|||||||
<CardTitle>{t('create.preview')}</CardTitle>
|
<CardTitle>{t('create.preview')}</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="text-center">
|
<CardContent className="text-center">
|
||||||
<div id="create-qr-preview" className="flex justify-center mb-4">
|
<div id="create-qr-preview" className="flex justify-center mb-4 w-full min-w-0 overflow-hidden">
|
||||||
{/* WRAPPER FOR REF AND FRAME */}
|
{/* WRAPPER FOR REF AND FRAME */}
|
||||||
<div
|
<div
|
||||||
ref={qrRef}
|
ref={qrRef}
|
||||||
className="relative bg-white rounded-xl p-4 flex flex-col items-center justify-center transition-all duration-300"
|
className="relative bg-white rounded-xl p-4 flex flex-col items-center justify-center transition-all duration-300 w-full max-w-full min-w-0"
|
||||||
style={{
|
style={{
|
||||||
minWidth: '280px',
|
|
||||||
minHeight: '280px',
|
minHeight: '280px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -1060,7 +1103,7 @@ export default function CreatePage() {
|
|||||||
|
|
||||||
{contentType === 'BARCODE' ? (
|
{contentType === 'BARCODE' ? (
|
||||||
qrContent ? (
|
qrContent ? (
|
||||||
<div className="p-2 bg-white">
|
<div className="p-2 bg-white w-full max-w-full [&_svg]:!w-full [&_svg]:!h-auto [&_svg]:!max-w-full">
|
||||||
<Barcode
|
<Barcode
|
||||||
key={`${qrContent}-${content.format}-${foregroundColor}`}
|
key={`${qrContent}-${content.format}-${foregroundColor}`}
|
||||||
value={qrContent}
|
value={qrContent}
|
||||||
@@ -1068,9 +1111,14 @@ export default function CreatePage() {
|
|||||||
lineColor={foregroundColor}
|
lineColor={foregroundColor}
|
||||||
background={backgroundColor}
|
background={backgroundColor}
|
||||||
width={2}
|
width={2}
|
||||||
height={100}
|
height={80}
|
||||||
|
margin={10}
|
||||||
displayValue={true}
|
displayValue={true}
|
||||||
|
fontSize={14}
|
||||||
/>
|
/>
|
||||||
|
<p className="mt-2 text-center text-[10px] leading-tight text-gray-600 px-2">
|
||||||
|
Scan: iPhone → Barcode Scanner App · Android → Google Lens / Camera
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-[200px] h-[200px] bg-gray-100 rounded flex items-center justify-center text-gray-500">
|
<div className="w-[200px] h-[200px] bg-gray-100 rounded flex items-center justify-center text-gray-500">
|
||||||
|
|||||||
@@ -8,6 +8,41 @@ import { Badge } from '@/components/ui/Badge';
|
|||||||
import { Dropdown, DropdownItem } from '@/components/ui/Dropdown';
|
import { Dropdown, DropdownItem } from '@/components/ui/Dropdown';
|
||||||
import { formatDate } from '@/lib/utils';
|
import { formatDate } from '@/lib/utils';
|
||||||
|
|
||||||
|
function addBarcodeCaptionToSvg(svgElement: SVGElement, caption: string): string {
|
||||||
|
const cloned = svgElement.cloneNode(true) as SVGElement;
|
||||||
|
const NS = 'http://www.w3.org/2000/svg';
|
||||||
|
|
||||||
|
const widthAttr = cloned.getAttribute('width');
|
||||||
|
const heightAttr = cloned.getAttribute('height');
|
||||||
|
const width = widthAttr ? parseFloat(widthAttr) : 200;
|
||||||
|
const height = heightAttr ? parseFloat(heightAttr) : 100;
|
||||||
|
const extraHeight = 18;
|
||||||
|
|
||||||
|
cloned.setAttribute('height', String(height + extraHeight));
|
||||||
|
const viewBox = cloned.getAttribute('viewBox');
|
||||||
|
if (viewBox) {
|
||||||
|
const parts = viewBox.split(/\s+/);
|
||||||
|
if (parts.length === 4) {
|
||||||
|
cloned.setAttribute(
|
||||||
|
'viewBox',
|
||||||
|
`${parts[0]} ${parts[1]} ${parts[2]} ${parseFloat(parts[3]) + extraHeight}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = document.createElementNS(NS, 'text');
|
||||||
|
text.setAttribute('x', String(width / 2));
|
||||||
|
text.setAttribute('y', String(height + 12));
|
||||||
|
text.setAttribute('text-anchor', 'middle');
|
||||||
|
text.setAttribute('font-size', '9');
|
||||||
|
text.setAttribute('font-family', 'Arial, Helvetica, sans-serif');
|
||||||
|
text.setAttribute('fill', '#666666');
|
||||||
|
text.textContent = caption;
|
||||||
|
cloned.appendChild(text);
|
||||||
|
|
||||||
|
return new XMLSerializer().serializeToString(cloned);
|
||||||
|
}
|
||||||
|
|
||||||
interface QRCodeCardProps {
|
interface QRCodeCardProps {
|
||||||
qr: {
|
qr: {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -113,7 +148,9 @@ END:VCARD`;
|
|||||||
const svgContainer = document.querySelector(`#qr-svg-${qr.id}`);
|
const svgContainer = document.querySelector(`#qr-svg-${qr.id}`);
|
||||||
const svg = svgContainer?.querySelector('svg');
|
const svg = svgContainer?.querySelector('svg');
|
||||||
if (svg) {
|
if (svg) {
|
||||||
let svgData = new XMLSerializer().serializeToString(svg);
|
let svgData = qr.contentType === 'BARCODE'
|
||||||
|
? addBarcodeCaptionToSvg(svg as SVGElement, 'Scan: iPhone -> Barcode Scanner App | Android -> Google Lens')
|
||||||
|
: new XMLSerializer().serializeToString(svg);
|
||||||
|
|
||||||
if (qr.style?.cornerStyle === 'rounded') {
|
if (qr.style?.cornerStyle === 'rounded') {
|
||||||
const width = svg.getAttribute('width') || '96';
|
const width = svg.getAttribute('width') || '96';
|
||||||
@@ -210,17 +247,23 @@ END:VCARD`;
|
|||||||
)}
|
)}
|
||||||
<div id={`qr-svg-${qr.id}`} className={qr.style?.cornerStyle === 'rounded' ? 'rounded-lg overflow-hidden' : ''}>
|
<div id={`qr-svg-${qr.id}`} className={qr.style?.cornerStyle === 'rounded' ? 'rounded-lg overflow-hidden' : ''}>
|
||||||
{qr.contentType === 'BARCODE' ? (
|
{qr.contentType === 'BARCODE' ? (
|
||||||
|
<div className="w-full max-w-[240px] [&_svg]:!w-full [&_svg]:!h-auto [&_svg]:!max-w-full">
|
||||||
<Barcode
|
<Barcode
|
||||||
key={`${qr.id}-${qr.type === 'STATIC' ? qr.content?.value : qrUrl}-${qr.content?.format}`}
|
key={`${qr.id}-${qr.type === 'STATIC' ? qr.content?.value : qrUrl}-${qr.content?.format}`}
|
||||||
value={qr.type === 'STATIC' ? (qr.content?.value || '123456789') : qrUrl}
|
value={qr.type === 'STATIC' ? (qr.content?.value || '123456789') : qrUrl}
|
||||||
format={(qr.content?.format as any) || 'CODE128'}
|
format={(qr.content?.format as any) || 'CODE128'}
|
||||||
lineColor={qr.style?.foregroundColor || '#000000'}
|
lineColor={qr.style?.foregroundColor || '#000000'}
|
||||||
background={qr.style?.backgroundColor || '#FFFFFF'}
|
background={qr.style?.backgroundColor || '#FFFFFF'}
|
||||||
width={1.5}
|
width={2}
|
||||||
height={60}
|
height={70}
|
||||||
|
margin={8}
|
||||||
displayValue={true}
|
displayValue={true}
|
||||||
fontSize={10}
|
fontSize={11}
|
||||||
/>
|
/>
|
||||||
|
<p className="mt-1 text-center text-[9px] leading-tight text-gray-600 px-1">
|
||||||
|
Scan: iPhone → Barcode Scanner App · Android → Google Lens
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<QRCodeSVG
|
<QRCodeSVG
|
||||||
value={qrUrl}
|
value={qrUrl}
|
||||||
|
|||||||
Reference in New Issue
Block a user