Render QR code with SVG, not canvas
This commit is contained in:
parent
7486312e4e
commit
eba8d8d4b8
4 changed files with 24 additions and 50 deletions
|
@ -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}
|
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
|
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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",
|
||||||
|
|
Loading…
Reference in a new issue