Render QR code with SVG, not canvas

This commit is contained in:
Evan Hahn 2022-01-14 10:45:05 -06:00 committed by GitHub
parent 7486312e4e
commit eba8d8d4b8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 24 additions and 50 deletions

View file

@ -10,8 +10,7 @@ const story = storiesOf('Components/QrCode', module);
story.add('Default', () => ( story.add('Default', () => (
<QrCode <QrCode
aria-label="Scan this little code!" alt="Scan this little code!"
data="sgnl://linkdevice?uuid=gCkj0T2xiSUaPRhMYiF24w&pub_key=7RshtQrb3UTMowITe79uW9dgw_CLTGWenj0OT80i0HpH" data="sgnl://linkdevice?uuid=gCkj0T2xiSUaPRhMYiF24w&pub_key=7RshtQrb3UTMowITe79uW9dgw_CLTGWenj0OT80i0HpH"
size={256}
/> />
)); ));

View file

@ -2,53 +2,33 @@
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import type { ReactElement } from 'react'; import type { ReactElement } from 'react';
import React, { useEffect, useMemo, useRef } from 'react'; import React, { useMemo, useRef } from 'react';
import qrcode from 'qrcode-generator'; import qrcode from 'qrcode-generator';
import { getEnvironment, Environment } from '../environment'; import { getEnvironment, Environment } from '../environment';
import { strictAssert } from '../util/assert';
import { useDevicePixelRatio } from '../hooks/useDevicePixelRatio';
const AUTODETECT_TYPE_NUMBER = 0; const AUTODETECT_TYPE_NUMBER = 0;
const ERROR_CORRECTION_LEVEL = 'L'; const ERROR_CORRECTION_LEVEL = 'L';
type PropsType = Readonly<{ type PropsType = Readonly<{
'aria-label': string; alt: string;
className?: string; className?: string;
data: string; data: string;
size: number;
}>; }>;
export function QrCode(props: PropsType): ReactElement { export function QrCode(props: PropsType): ReactElement {
// I don't think it's possible to destructure this. const { alt, className, data } = props;
// eslint-disable-next-line react/destructuring-assignment
const ariaLabel = props['aria-label'];
const { className, data, size } = props;
const qrCode = useMemo(() => { const elRef = useRef<null | HTMLImageElement>(null);
const result = qrcode(AUTODETECT_TYPE_NUMBER, ERROR_CORRECTION_LEVEL);
result.addData(data); const src = useMemo(() => {
result.make(); const qrCode = qrcode(AUTODETECT_TYPE_NUMBER, ERROR_CORRECTION_LEVEL);
return result; qrCode.addData(data);
qrCode.make();
const svgData = qrCode.createSvgTag({ cellSize: 1, margin: 0 });
return `data:image/svg+xml;utf8,${svgData}`;
}, [data]); }, [data]);
const canvasRef = useRef<null | HTMLCanvasElement>(null);
const dpi = useDevicePixelRatio();
const canvasSize = size * dpi;
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) {
return;
}
const context = canvas.getContext('2d');
strictAssert(context, 'Expected a canvas context');
const cellSize = canvasSize / qrCode.getModuleCount();
context.clearRect(0, 0, canvasSize, canvasSize);
qrCode.renderTo2dContext(context, cellSize);
}, [canvasSize, qrCode]);
// Add a development-only feature to copy a QR code to the clipboard by double-clicking. // Add a development-only feature to copy a QR code to the clipboard by double-clicking.
// This can be used to quickly inspect the code, or to link this Desktop with an iOS // This can be used to quickly inspect the code, or to link this Desktop with an iOS
// simulator primary, which has a debug-only option to paste the linking URL instead of // simulator primary, which has a debug-only option to paste the linking URL instead of
@ -60,25 +40,23 @@ export function QrCode(props: PropsType): ReactElement {
navigator.clipboard.writeText(data); navigator.clipboard.writeText(data);
const canvas = canvasRef.current; const el = elRef.current;
if (!canvas) { if (!el) {
return; return;
} }
canvas.style.filter = 'brightness(50%)'; el.style.filter = 'brightness(50%)';
window.setTimeout(() => { window.setTimeout(() => {
canvas.style.filter = ''; el.style.filter = '';
}, 150); }, 150);
}; };
return ( return (
<canvas <img
aria-label={ariaLabel} alt={alt}
className={className} className={className}
height={canvasSize}
ref={canvasRef}
style={{ width: size, height: size }}
width={canvasSize}
onDoubleClick={onDoubleClick} onDoubleClick={onDoubleClick}
ref={elRef}
src={src}
/> />
); );
} }

View file

@ -25,8 +25,6 @@ type PropsType = {
provisioningUrl: Loadable<string>; provisioningUrl: Loadable<string>;
}; };
// This should match the size in the CSS.
const QR_CODE_SIZE = 256;
const QR_CODE_FAILED_LINK = const QR_CODE_FAILED_LINK =
'https://support.signal.org/hc/articles/360007320451#desktop_multiple_device'; 'https://support.signal.org/hc/articles/360007320451#desktop_multiple_device';
@ -113,10 +111,9 @@ function InstallScreenQrCode(
case LoadingState.Loaded: case LoadingState.Loaded:
contents = ( contents = (
<QrCode <QrCode
aria-label={i18n('Install__scan-this-code')} alt={i18n('Install__scan-this-code')}
className={getQrCodeClassName('__code')} className={getQrCodeClassName('__code')}
data={props.value} data={props.value}
size={QR_CODE_SIZE}
/> />
); );
break; break;

View file

@ -7527,10 +7527,10 @@
{ {
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/components/QrCode.tsx", "path": "ts/components/QrCode.tsx",
"line": " const canvasRef = useRef<null | HTMLCanvasElement>(null);", "line": " const elRef = useRef<null | HTMLImageElement>(null);",
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2022-01-04T21:43:17.517Z", "updated": "2022-01-04T21:43:17.517Z",
"reasonDetail": "Used to draw on the canvas." "reasonDetail": "Used to change the style in non-production builds."
}, },
{ {
"rule": "React-createRef", "rule": "React-createRef",