fix: saved qr codes will be black and white by default

qr code logos can now be svgs for better rendering performance
pull/3083/head
William Grant 12 months ago
parent 8a4e5d52ff
commit 44cc8594a5

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 927 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 927 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 33 40" fill="none"><path fill="#000" stroke="#000" stroke-width=".3" d="m27.347 20.56-6.521-4.026h4.42c1.849 0 3.622-.818 4.929-2.275 1.307-1.457 2.041-3.432 2.041-5.492s-.734-4.036-2.041-5.492C28.867 1.818 27.095 1 25.246 1H9.813c-2.315.003-4.534 1.03-6.171 2.853-1.637 1.824-2.558 4.297-2.56 6.877 0 1.88.453 3.726 1.31 5.345.857 1.62 2.087 2.954 3.563 3.865l6.522 4.027H8.051a6.346 6.346 0 0 0-2.692.565 6.956 6.956 0 0 0-2.289 1.677 7.869 7.869 0 0 0-1.532 2.53A8.542 8.542 0 0 0 1 31.734c0 1.028.183 2.045.538 2.993a7.87 7.87 0 0 0 1.532 2.531 6.957 6.957 0 0 0 2.29 1.677c.854.385 1.77.577 2.691.566h15.433c2.315-.003 4.535-1.03 6.172-2.853 1.637-1.825 2.558-4.298 2.56-6.877 0-1.88-.452-3.725-1.308-5.345-.856-1.62-2.086-2.954-3.56-3.865Zm-20.35-2.725c-1.113-.688-2.044-1.687-2.703-2.9a8.822 8.822 0 0 1-1.05-4.014C3.156 6.79 6.27 3.406 9.979 3.406h15.128c2.587 0 4.816 2.207 4.944 5.09a5.927 5.927 0 0 1-.295 2.133 5.493 5.493 0 0 1-1.025 1.83c-.45.526-.99.945-1.59 1.232a4.395 4.395 0 0 1-1.895.435h-8.712a.918.918 0 0 0-.68.318c-.18.202-.28.475-.28.76v7.923l-8.577-5.292Zm16.321 19.258H8.191c-2.587 0-4.816-2.207-4.944-5.09a5.928 5.928 0 0 1 .295-2.131 5.491 5.491 0 0 1 1.025-1.83c.45-.527.99-.946 1.588-1.233a4.395 4.395 0 0 1 1.896-.436h8.713a.88.88 0 0 0 .37-.082.966.966 0 0 0 .313-.233c.09-.1.16-.219.209-.35.048-.13.073-.27.073-.411v-7.925l8.571 5.293c1.114.687 2.046 1.688 2.705 2.902a8.82 8.82 0 0 1 1.05 4.017c.087 4.126-3.027 7.509-6.737 7.509Z"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="56" height="56" viewBox="0 0 46 56" fill="none"><path fill="#000" fill-rule="evenodd" d="M21.013.459A4.578 4.578 0 0 1 22.989 0l.01.02c.655 0 1.302.146 1.948.449.788.38 1.596.742 2.404 1.103l.93.42 4.381 2.012c1.425.65 2.85 1.303 4.276 1.955l.003.002c2.062.943 4.126 1.888 6.192 2.829 1.948.888 3.012 2.695 2.85 4.795-.011.114-.008.268-.004.443.003.095.005.196.005.3.01.243.01.497.01.79a378.737 378.737 0 0 0 0 8.048v.049l-.01.048V24.206c-.01.942-.02 1.894-.048 2.817-.047 2.452-.503 4.776-.902 6.524l-.029.117-.038.118c-.342 1.191-.731 2.46-1.235 3.77-.675 1.718-1.558 3.486-2.708 5.41l-.048.078-.057.078c-1.358 2.051-2.622 3.653-3.971 5.05a29.438 29.438 0 0 1-7.44 5.596c-.043.024-.088.05-.133.078l-.133.078-.143.078-.143.068c-1.33.606-2.917 1.28-4.655 1.758l-.038.02h-.039a5.066 5.066 0 0 1-2.527-.01 26.956 26.956 0 0 1-4.912-1.885l-.029-.03h-.029c-4.665-2.402-8.542-5.957-11.525-10.557l-.133-.156-.143-.244c-1.036-1.748-1.815-3.272-2.451-4.786-.522-1.253-.906-2.478-1.279-3.666l-.023-.074-.143-.45-.066-.224-.048-.224-.085-.435-.086-.435-.021-.107c-.193-.963-.392-1.953-.51-2.998C0 27.848.002 26.218.003 24.656V14.24l.002-.382c.002-.237.004-.459-.002-.664-.076-1.894.893-3.496 2.651-4.326l5.611-2.564 5.611-2.564 3.392-1.552.143-.069.237-.117.076-.039.44-.201c.267-.123.536-.246.805-.365.199-.091.397-.181.596-.27v-.001c.485-.22.968-.438 1.447-.667Zm4.854 25.24 4.432 2.492a6.468 6.468 0 0 1 2.42 2.393 6.592 6.592 0 0 1 .89 3.307 6.073 6.073 0 0 1-1.74 4.256 5.898 5.898 0 0 1-4.195 1.766h-10.49a4.676 4.676 0 0 1-1.829-.35 4.731 4.731 0 0 1-1.555-1.038c-.446-.447-.8-.98-1.042-1.566a4.869 4.869 0 0 1 0-3.705 4.811 4.811 0 0 1 1.042-1.566c.445-.447.974-.8 1.555-1.038a4.676 4.676 0 0 1 1.83-.35h3.008l-4.433-2.492a6.47 6.47 0 0 1-2.422-2.392 6.593 6.593 0 0 1-.89-3.308 6.074 6.074 0 0 1 1.74-4.256 5.898 5.898 0 0 1 4.194-1.766h10.49c1.256 0 2.46.507 3.349 1.408a4.843 4.843 0 0 1 1.388 3.4c0 1.274-.5 2.497-1.388 3.398a4.703 4.703 0 0 1-3.35 1.408h-3.004Zm-11.236-.989a4.99 4.99 0 0 0 1.837 1.795l5.83 3.275v-4.903a.67.67 0 0 1 .19-.47.65.65 0 0 1 .462-.197h5.921c.443 0 .882-.092 1.289-.27a3.27 3.27 0 0 0 1.08-.762 3.368 3.368 0 0 0 .897-2.452c-.087-1.785-1.602-3.15-3.36-3.15H18.494c-2.52 0-4.637 2.093-4.578 4.65.02.877.267 1.734.715 2.484Zm2.649 13.714H27.56c2.522 0 4.638-2.094 4.578-4.648a5.083 5.083 0 0 0-.713-2.486 4.988 4.988 0 0 0-1.838-1.796l-5.826-3.275v4.904a.673.673 0 0 1-.192.471.655.655 0 0 1-.463.195h-5.922c-.443 0-.881.093-1.288.27a3.273 3.273 0 0 0-1.08.763 3.364 3.364 0 0 0-.897 2.452c.087 1.784 1.602 3.15 3.36 3.15Z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1010 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1010 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1017 B

@ -1,18 +1,33 @@
import { MouseEvent } from 'react';
import { isEmpty } from 'lodash';
import { CSSProperties, MouseEvent, useState } from 'react';
import { QRCode } from 'react-qrcode-logo';
import styled, { CSSProperties } from 'styled-components';
import { THEME_GLOBALS } from '../themes/globals';
import { useMount } from 'react-use';
import styled from 'styled-components';
import { THEME_GLOBALS, getThemeValue } from '../themes/globals';
import { saveQRCode } from '../util/saveQRCode';
import { AnimatedFlex } from './basic/Flex';
// We fade in the QR code to hide the logo flickering on first render
const StyledQRView = styled(AnimatedFlex)`
/** AnimatedFlex because we fade in the QR code to hide the logo flickering on first render
* The container controls the visible width and height of the QR code because we lose quality if the html canvas size is too small so we scale down with CSS.
*/
const StyledQRView = styled(AnimatedFlex)<{
canvasId?: string;
size?: number;
backgroundColor?: string;
}>`
cursor: pointer;
border-radius: var(--border-radius);
border-radius: 10px;
overflow: hidden;
padding: 10px;
${props => props.backgroundColor && ` background-color: ${props.backgroundColor}`};
${props =>
props.canvasId &&
props.size &&
`#${props.canvasId} { width: ${props.size}px !important; height: ${props.size}px !important; }`}
`;
type Props = {
export type SessionQRCodeProps = {
id: string;
value: string;
size: number;
@ -21,29 +36,79 @@ type Props = {
logoImage?: string;
logoWidth?: number;
logoHeight?: number;
logoIsSVG?: boolean;
style?: CSSProperties;
ignoreTheme?: boolean;
};
export function SessionQRCode(props: Props) {
export function SessionQRCode(props: SessionQRCodeProps) {
const {
id,
value,
size,
backgroundColor = '#FFF',
foregroundColor = '#000',
backgroundColor = 'white',
foregroundColor = 'black',
logoImage,
logoWidth,
logoHeight,
logoIsSVG,
style,
ignoreTheme,
} = props;
const [svgDataURL, setSvgDataURL] = useState('');
// Saving the QR code uses a separate react instance without the redux store so we don't use a selector here
const theme = window.Events.getThemeSetting();
useMount(() => {
if (logoImage && logoIsSVG && isEmpty(svgDataURL)) {
// eslint-disable-next-line more/no-then
fetch(logoImage)
.then(response => response.text())
.then(response => {
const svgString = ignoreTheme
? response
: response.replaceAll(
'black',
getThemeValue(
theme.includes('dark') ? '--background-primary-color' : '--text-primary-color'
)
);
setSvgDataURL(`data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgString)}`);
})
.catch(error =>
window.log.error('Error fetching the QR Code logo which is an svg:', error)
);
}
});
if (logoImage && logoIsSVG && isEmpty(svgDataURL)) {
return null;
}
const qrCanvasSize = 1000;
const canvasLogoWidth =
logoWidth && logoHeight ? (qrCanvasSize * 0.25 * logoWidth) / logoHeight : undefined;
const canvasLogoHeight = logoHeight ? (qrCanvasSize * 0.25 * logoHeight) / logoHeight : undefined;
return (
<StyledQRView
container={true}
aria-label={window.i18n('clickToTrustContact')}
title={window.i18n('clickToTrustContact')}
onClick={(event: MouseEvent) => {
canvasId={id}
size={size}
backgroundColor={backgroundColor}
onClick={(event: MouseEvent<HTMLDivElement>) => {
event.preventDefault();
saveQRCode(id);
void saveQRCode(id, {
...props,
id: `temp-${props.id}`,
backgroundColor: 'white',
foregroundColor: 'black',
ignoreTheme: true,
style: { display: 'none' },
});
}}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
@ -54,13 +119,14 @@ export function SessionQRCode(props: Props) {
id={id}
value={value}
ecLevel={'Q'}
size={size}
quietZone={10}
size={qrCanvasSize}
bgColor={backgroundColor}
fgColor={foregroundColor}
logoImage={logoImage}
logoWidth={logoWidth}
logoHeight={logoHeight}
quietZone={0}
logoImage={logoIsSVG ? svgDataURL : logoImage}
logoWidth={canvasLogoWidth}
logoHeight={canvasLogoHeight}
removeQrCodeBehindLogo={true}
/>
</StyledQRView>
);

@ -34,9 +34,10 @@ const QRView = ({ sessionID }: { sessionID: string }) => {
foregroundColor={getThemeValue(
theme.includes('dark') ? '--background-primary-color' : '--text-primary-color'
)}
logoImage={`./images/session/brand/${theme}.png`}
logoWidth={42}
logoHeight={44}
logoImage={'./images/session/qr/brand.svg'}
logoWidth={40}
logoHeight={40}
logoIsSVG={true}
/>
);
};

@ -142,9 +142,10 @@ const Seed = (props: SeedProps) => {
foregroundColor={getThemeValue(
theme.includes('dark') ? '--background-primary-color' : '--text-primary-color'
)}
logoImage={`./images/session/shield/${theme}.png`}
logoWidth={60}
logoHeight={60}
logoImage={'./images/session/qr/shield.svg'}
logoWidth={56}
logoHeight={56}
logoIsSVG={true}
style={{ margin: '0 auto var(--margins-lg)' }}
/>

@ -1,14 +0,0 @@
import { saveURLAsFile } from './saveURLAsFile';
export function saveQRCode(id: string): void {
const qrCanvas = document.querySelector(`#${id}`) as HTMLCanvasElement;
if (qrCanvas) {
saveURLAsFile({
filename: `${id}-${new Date().toISOString()}.png`,
url: qrCanvas.toDataURL(),
document,
});
} else {
window.log.error('[saveQRCode] QR code not found!');
}
}

@ -0,0 +1,34 @@
import { isEmpty } from 'lodash';
import { createRoot } from 'react-dom/client';
import { SessionQRCode, SessionQRCodeProps } from '../components/SessionQRCode';
import { sleepFor } from '../session/utils/Promise';
import { saveURLAsFile } from './saveURLAsFile';
export async function saveQRCode(id: string, customProps?: SessionQRCodeProps): Promise<void> {
let qrCanvas: HTMLCanvasElement | undefined;
if (!isEmpty(customProps)) {
const root = document.querySelector('#root');
const divElement = document.createElement('div');
root?.appendChild(divElement);
const reactRoot = createRoot(divElement!);
reactRoot.render(<SessionQRCode {...customProps} />);
// wait for it to render
await sleepFor(100);
qrCanvas = root?.querySelector(`#${customProps.id}`) as HTMLCanvasElement;
reactRoot?.unmount();
root?.removeChild(divElement);
} else {
qrCanvas = document.querySelector(`#${id}`) as HTMLCanvasElement;
}
if (qrCanvas) {
saveURLAsFile({
filename: `${id}-${new Date().toISOString()}.png`,
url: qrCanvas.toDataURL(),
document,
});
} else {
window.log.error('[saveQRCode] QR code not found!');
}
}
Loading…
Cancel
Save