Skip to content

Commit 025986a

Browse files
commrelayunitC-3PO
andauthored
Download custom QR codes as high-res PNG (#118)
Co-authored-by: C-3PO <c3po.bot@gmail.com>
1 parent 089d02d commit 025986a

1 file changed

Lines changed: 44 additions & 8 deletions

File tree

src/components/vaults/QRCodeDialog.tsx

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { cn } from '@/lib/utils';
2727
const CUSTOM_QR_ENDPOINT = 'https://refhub-qr.netlify.app/api/generate-qr';
2828
const CUSTOM_QR_FREEDOM = 0;
2929
const QR_DOWNLOAD_BACKGROUND = '#0d0d0f';
30+
const QR_DOWNLOAD_SIZE = 2048;
3031

3132
interface QRCodeDialogProps {
3233
vault: Vault;
@@ -182,23 +183,58 @@ export function QRCodeDialog({ vault, onVaultUpdate }: QRCodeDialogProps) {
182183
return svg.replace(/(<svg\b[^>]*>)/, `$1${background}`);
183184
};
184185

185-
const downloadQR = () => {
186+
const downloadPngFromSvg = async (svg: string) => {
187+
const canvas = document.createElement('canvas');
188+
canvas.width = QR_DOWNLOAD_SIZE;
189+
canvas.height = QR_DOWNLOAD_SIZE;
190+
191+
const context = canvas.getContext('2d');
192+
if (!context) throw new Error('unable to prepare qr download');
193+
194+
const svgForDownload = getDownloadableSvg(svg);
195+
const blobUrl = URL.createObjectURL(new Blob([svgForDownload], { type: 'image/svg+xml' }));
196+
197+
try {
198+
await new Promise<void>((resolve, reject) => {
199+
const image = new Image();
200+
image.onload = () => {
201+
context.clearRect(0, 0, QR_DOWNLOAD_SIZE, QR_DOWNLOAD_SIZE);
202+
context.drawImage(image, 0, 0, QR_DOWNLOAD_SIZE, QR_DOWNLOAD_SIZE);
203+
resolve();
204+
};
205+
image.onerror = () => reject(new Error('unable to render qr download'));
206+
image.src = blobUrl;
207+
});
208+
} finally {
209+
URL.revokeObjectURL(blobUrl);
210+
}
211+
212+
const pngUrl = canvas.toDataURL('image/png');
186213
const link = document.createElement('a');
214+
link.download = `${vault.name || 'vault'}-qr.png`;
215+
link.href = pngUrl;
216+
link.click();
217+
};
187218

219+
const downloadQR = async () => {
188220
if (customQrSvg) {
189-
const svgForDownload = getDownloadableSvg(customQrSvg);
190-
const blobUrl = URL.createObjectURL(new Blob([svgForDownload], { type: 'image/svg+xml' }));
191-
link.download = `${vault.name || 'vault'}-qr.svg`;
192-
link.href = blobUrl;
193-
link.click();
194-
URL.revokeObjectURL(blobUrl);
195-
toast({ title: 'qr_code_downloaded' });
221+
try {
222+
await downloadPngFromSvg(customQrSvg);
223+
toast({ title: 'qr_code_downloaded' });
224+
} catch (error) {
225+
toast({
226+
title: 'error_downloading_qr_code',
227+
description: (error as Error).message,
228+
variant: 'destructive',
229+
});
230+
}
196231
return;
197232
}
198233

199234
const canvas = qrRef.current?.querySelector('canvas');
200235
if (!canvas) return;
201236

237+
const link = document.createElement('a');
202238
link.download = `${vault.name || 'vault'}-qr.png`;
203239
link.href = canvas.toDataURL('image/png');
204240
link.click();

0 commit comments

Comments
 (0)