You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			149 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			TypeScript
		
	
			
		
		
	
	
			149 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			TypeScript
		
	
| import { MouseEvent, useEffect, useRef, useState } from 'react';
 | |
| import { QRCode } from 'react-qrcode-logo';
 | |
| import styled, { CSSProperties } from 'styled-components';
 | |
| import { THEME_GLOBALS } from '../themes/globals';
 | |
| import { renderQRCode } from '../util/qrCodes';
 | |
| import { AnimatedFlex } from './basic/Flex';
 | |
| import { SessionIconType } from './icon';
 | |
| 
 | |
| // AnimatedFlex because we fade in the QR code a flicker on first render
 | |
| const StyledQRView = styled(AnimatedFlex)<{
 | |
|   size: number;
 | |
| }>`
 | |
|   cursor: pointer;
 | |
|   border-radius: 10px;
 | |
|   overflow: hidden;
 | |
|   ${props => props.size && `width: ${props.size}px; height: ${props.size}px;`}
 | |
| `;
 | |
| 
 | |
| export type QRCodeLogoProps = { iconType: SessionIconType; iconSize: number };
 | |
| 
 | |
| export type SessionQRCodeProps = {
 | |
|   id: string;
 | |
|   value: string;
 | |
|   size: number;
 | |
|   backgroundColor?: string;
 | |
|   foregroundColor?: string;
 | |
|   hasLogo?: QRCodeLogoProps;
 | |
|   logoImage?: string;
 | |
|   logoSize?: number;
 | |
|   loading?: boolean;
 | |
|   onClick?: (fileName: string, dataUrl: string) => void;
 | |
|   ariaLabel?: string;
 | |
|   dataTestId?: string;
 | |
|   style?: CSSProperties;
 | |
| };
 | |
| 
 | |
| export function SessionQRCode(props: SessionQRCodeProps) {
 | |
|   const {
 | |
|     id,
 | |
|     value,
 | |
|     size,
 | |
|     backgroundColor,
 | |
|     foregroundColor,
 | |
|     hasLogo,
 | |
|     logoImage,
 | |
|     logoSize,
 | |
|     loading,
 | |
|     onClick,
 | |
|     ariaLabel,
 | |
|     dataTestId,
 | |
|     style,
 | |
|   } = props;
 | |
|   const [logo, setLogo] = useState(logoImage);
 | |
|   const [bgColor, setBgColor] = useState(backgroundColor);
 | |
|   const [fgColor, setFgColor] = useState(foregroundColor);
 | |
| 
 | |
|   const qrRef = useRef<QRCode>(null);
 | |
|   const qrCanvasSize = 1000;
 | |
|   const canvasLogoSize = logoSize ? (qrCanvasSize * 0.25 * logoSize) / logoSize : 250;
 | |
| 
 | |
|   const loadQRCodeDataUrl = async () => {
 | |
|     const fileName = `${id}-${new Date().toISOString()}.jpg`;
 | |
|     let url = '';
 | |
| 
 | |
|     try {
 | |
|       url = await renderQRCode(
 | |
|         {
 | |
|           id: `${id}-save`,
 | |
|           value,
 | |
|           size,
 | |
|           hasLogo,
 | |
|           logoImage,
 | |
|           logoSize,
 | |
|         },
 | |
|         fileName
 | |
|       );
 | |
|     } catch (err) {
 | |
|       window.log.error(`QR code save failed! ${fileName}\n${err}`);
 | |
|     }
 | |
|     return { fileName, url };
 | |
|   };
 | |
| 
 | |
|   const handleOnClick = async () => {
 | |
|     const { fileName, url } = await loadQRCodeDataUrl();
 | |
|     if (onClick) {
 | |
|       onClick(fileName, url);
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   useEffect(() => {
 | |
|     // Don't pass the component props to the QR component directly instead update it's props in the next render cycle to prevent janky renders
 | |
|     if (loading) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (bgColor !== backgroundColor) {
 | |
|       setBgColor(backgroundColor);
 | |
|     }
 | |
| 
 | |
|     if (fgColor !== foregroundColor) {
 | |
|       setFgColor(foregroundColor);
 | |
|     }
 | |
| 
 | |
|     if (hasLogo && logo !== logoImage) {
 | |
|       setLogo(logoImage);
 | |
|     }
 | |
|   }, [backgroundColor, bgColor, fgColor, foregroundColor, hasLogo, loading, logo, logoImage]);
 | |
| 
 | |
|   return (
 | |
|     <StyledQRView
 | |
|       container={true}
 | |
|       justifyContent="center"
 | |
|       alignItems="center"
 | |
|       size={size}
 | |
|       id={id}
 | |
|       title={window.i18n('download')}
 | |
|       aria-label={ariaLabel || 'QR code'}
 | |
|       onClick={(event: MouseEvent<HTMLDivElement>) => {
 | |
|         event.preventDefault();
 | |
|         void handleOnClick();
 | |
|       }}
 | |
|       data-testId={dataTestId || 'session-qr-code'}
 | |
|       initial={{ opacity: 0 }}
 | |
|       animate={{ opacity: loading ? 0 : 1 }}
 | |
|       transition={{ duration: THEME_GLOBALS['--default-duration-seconds'] }}
 | |
|       style={style}
 | |
|     >
 | |
|       <QRCode
 | |
|         ref={qrRef}
 | |
|         id={`${id}-canvas`}
 | |
|         value={value}
 | |
|         ecLevel={'Q'}
 | |
|         size={qrCanvasSize}
 | |
|         bgColor={bgColor}
 | |
|         fgColor={fgColor}
 | |
|         quietZone={40}
 | |
|         logoImage={logo}
 | |
|         logoWidth={canvasLogoSize}
 | |
|         logoHeight={canvasLogoSize}
 | |
|         removeQrCodeBehindLogo={true}
 | |
|         style={{
 | |
|           width: size,
 | |
|           height: size,
 | |
|         }}
 | |
|       />
 | |
|     </StyledQRView>
 | |
|   );
 | |
| }
 |