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>
 | 
						|
  );
 | 
						|
}
 |