Skip to content

Commit

Permalink
Lazy load QR renderer
Browse files Browse the repository at this point in the history
  • Loading branch information
MananTank committed Feb 16, 2024
1 parent 1263c67 commit dc4901a
Show file tree
Hide file tree
Showing 2 changed files with 177 additions and 164 deletions.
194 changes: 30 additions & 164 deletions packages/thirdweb/src/react/ui/components/QRCode.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { keyframes } from "@emotion/react";
import React, { type ReactElement, useMemo } from "react";
import { encode } from "uqr";
import React, { Suspense, lazy } from "react";
import { useCustomTheme } from "../design-system/CustomThemeProvider.js";
import { fadeInAnimation } from "../design-system/animations.js";
import { StyledDiv } from "../design-system/elements.js";
import { radius } from "../design-system/index.js";

const QRCodeRenderer = /* @__PURE__ */ lazy(
() => import("./QRCode/QRCodeRenderer.js"),
);

/**
* @internal
*/
Expand All @@ -16,6 +19,20 @@ export const QRCode = (props: {
}) => {
const size = props.size || 310;

const placeholder = (
<QRPlaceholder
style={{
width: `${size}px`,
height: `${size}px`,
}}
>
<span data-v1 />
<span data-v2 />
<span data-v3 />
<div />
</QRPlaceholder>
);

return (
<div
style={{
Expand All @@ -25,26 +42,18 @@ export const QRCode = (props: {
}}
>
{props.qrCodeUri ? (
<QRCodeContainer>
<QRCodeRenderer
uri={props.qrCodeUri}
size={size + 20}
ecl="M"
clearSize={props.QRIcon ? 70 : undefined}
/>
</QRCodeContainer>
<Suspense fallback={placeholder}>
<QRCodeContainer>
<QRCodeRenderer
uri={props.qrCodeUri}
size={size + 20}
ecl="M"
clearSize={props.QRIcon ? 70 : undefined}
/>
</QRCodeContainer>
</Suspense>
) : (
<QRPlaceholder
style={{
width: `${size}px`,
height: `${size}px`,
}}
>
<span data-v1 />
<span data-v2 />
<span data-v3 />
<div />
</QRPlaceholder>
placeholder
)}

{props.QRIcon && <IconContainer>{props.QRIcon}</IconContainer>}
Expand Down Expand Up @@ -73,15 +82,6 @@ const QRCodeContainer = /* @__PURE__ */ StyledDiv(() => {
};
});

type Props = {
ecl?: "L" | "M" | "Q" | "H";
size?: number;
uri: string;
clearSize?: number;
image?: React.ReactNode;
imageBackground?: string;
};

const PlaceholderKeyframes = keyframes`
0%{ background-position: 100% 0; }
100%{ background-position: -100% 0; }
Expand Down Expand Up @@ -157,137 +157,3 @@ const QRPlaceholder = /* @__PURE__ */ StyledDiv(() => {
},
};
});

/**
* @internal
*/
function QRCodeRenderer({
ecl = "M",
size: sizeProp = 200,
uri,
clearSize = 0,
image,
imageBackground = "transparent",
}: Props) {
const logoSize = clearSize;
const size = sizeProp - 10 * 2;

const dots = useMemo(() => {
const dotsArray: ReactElement[] = [];
const matrix = encode(uri, { ecc: ecl, border: 0 }).data;
const cellSize = size / matrix.length;
const qrList = [
{ x: 0, y: 0 },
{ x: 1, y: 0 },
{ x: 0, y: 1 },
];

qrList.forEach(({ x, y }) => {
const x1 = (matrix.length - 7) * cellSize * x;
const y1 = (matrix.length - 7) * cellSize * y;
for (let i = 0; i < 3; i++) {
dotsArray.push(
<rect
key={`${i}-${x}-${y}`}
fill={
i % 2 !== 0
? "var(--ck-qr-background, var(--ck-body-background))"
: "var(--ck-qr-dot-color)"
}
rx={(i - 2) * -5 + (i === 0 ? 2 : 3)}
ry={(i - 2) * -5 + (i === 0 ? 2 : 3)}
width={cellSize * (7 - i * 2)}
height={cellSize * (7 - i * 2)}
x={x1 + cellSize * i}
y={y1 + cellSize * i}
/>,
);
}
});

if (image) {
const x1 = (matrix.length - 7) * cellSize * 1;
const y1 = (matrix.length - 7) * cellSize * 1;
dotsArray.push(
<>
<rect
fill={imageBackground}
rx={(0 - 2) * -5 + 2}
ry={(0 - 2) * -5 + 2}
width={cellSize * (7 - 0 * 2)}
height={cellSize * (7 - 0 * 2)}
x={x1 + cellSize * 0}
y={y1 + cellSize * 0}
/>
<foreignObject
width={cellSize * (7 - 0 * 2)}
height={cellSize * (7 - 0 * 2)}
x={x1 + cellSize * 0}
y={y1 + cellSize * 0}
>
<div style={{ borderRadius: (0 - 2) * -5 + 2, overflow: "hidden" }}>
{image}
</div>
</foreignObject>
</>,
);
}

const clearArenaSize = Math.floor((logoSize + 25) / cellSize);
const matrixMiddleStart = matrix.length / 2 - clearArenaSize / 2;
const matrixMiddleEnd = matrix.length / 2 + clearArenaSize / 2 - 1;

matrix.forEach((row, i: number) => {
row.forEach((_: any, j: number) => {
if (matrix[i]?.[j]) {
// Do not render dots under position squares
if (
!(
(i < 7 && j < 7) ||
(i > matrix.length - 8 && j < 7) ||
(i < 7 && j > matrix.length - 8)
)
) {
//if (image && i > matrix.length - 9 && j > matrix.length - 9) return;
if (
image ||
!(
i > matrixMiddleStart &&
i < matrixMiddleEnd &&
j > matrixMiddleStart &&
j < matrixMiddleEnd
)
) {
dotsArray.push(
<circle
key={`circle-${i}-${j}`}
cx={i * cellSize + cellSize / 2}
cy={j * cellSize + cellSize / 2}
fill="var(--ck-qr-dot-color)"
r={cellSize / 3}
/>,
);
}
}
}
});
});

return dotsArray;
}, [ecl, image, imageBackground, logoSize, size, uri]);

return (
<svg
height={size}
width={size}
viewBox={`0 0 ${size} ${size}`}
style={{
width: size,
height: size,
}}
>
<rect fill="transparent" height={size} width={size} />
{dots}
</svg>
);
}
147 changes: 147 additions & 0 deletions packages/thirdweb/src/react/ui/components/QRCode/QRCodeRenderer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import React, { type ReactElement, useMemo } from "react";
import { encode } from "uqr";

export type QRCodeRendererProps = {
ecl?: "L" | "M" | "Q" | "H";
size?: number;
uri: string;
clearSize?: number;
image?: React.ReactNode;
imageBackground?: string;
};

/**
* @internal
*/
function QRCodeRenderer({
ecl = "M",
size: sizeProp = 200,
uri,
clearSize = 0,
image,
imageBackground = "transparent",
}: QRCodeRendererProps) {
const logoSize = clearSize;
const size = sizeProp - 10 * 2;

const dots = useMemo(() => {
const dotsArray: ReactElement[] = [];
const matrix = encode(uri, { ecc: ecl, border: 0 }).data;
const cellSize = size / matrix.length;
const qrList = [
{ x: 0, y: 0 },
{ x: 1, y: 0 },
{ x: 0, y: 1 },
];

qrList.forEach(({ x, y }) => {
const x1 = (matrix.length - 7) * cellSize * x;
const y1 = (matrix.length - 7) * cellSize * y;
for (let i = 0; i < 3; i++) {
dotsArray.push(
<rect
key={`${i}-${x}-${y}`}
fill={
i % 2 !== 0
? "var(--ck-qr-background, var(--ck-body-background))"
: "var(--ck-qr-dot-color)"
}
rx={(i - 2) * -5 + (i === 0 ? 2 : 3)}
ry={(i - 2) * -5 + (i === 0 ? 2 : 3)}
width={cellSize * (7 - i * 2)}
height={cellSize * (7 - i * 2)}
x={x1 + cellSize * i}
y={y1 + cellSize * i}
/>,
);
}
});

if (image) {
const x1 = (matrix.length - 7) * cellSize * 1;
const y1 = (matrix.length - 7) * cellSize * 1;
dotsArray.push(
<>
<rect
fill={imageBackground}
rx={(0 - 2) * -5 + 2}
ry={(0 - 2) * -5 + 2}
width={cellSize * (7 - 0 * 2)}
height={cellSize * (7 - 0 * 2)}
x={x1 + cellSize * 0}
y={y1 + cellSize * 0}
/>
<foreignObject
width={cellSize * (7 - 0 * 2)}
height={cellSize * (7 - 0 * 2)}
x={x1 + cellSize * 0}
y={y1 + cellSize * 0}
>
<div style={{ borderRadius: (0 - 2) * -5 + 2, overflow: "hidden" }}>
{image}
</div>
</foreignObject>
</>,
);
}

const clearArenaSize = Math.floor((logoSize + 25) / cellSize);
const matrixMiddleStart = matrix.length / 2 - clearArenaSize / 2;
const matrixMiddleEnd = matrix.length / 2 + clearArenaSize / 2 - 1;

matrix.forEach((row, i: number) => {
row.forEach((_: any, j: number) => {
if (matrix[i]?.[j]) {
// Do not render dots under position squares
if (
!(
(i < 7 && j < 7) ||
(i > matrix.length - 8 && j < 7) ||
(i < 7 && j > matrix.length - 8)
)
) {
//if (image && i > matrix.length - 9 && j > matrix.length - 9) return;
if (
image ||
!(
i > matrixMiddleStart &&
i < matrixMiddleEnd &&
j > matrixMiddleStart &&
j < matrixMiddleEnd
)
) {
dotsArray.push(
<circle
key={`circle-${i}-${j}`}
cx={i * cellSize + cellSize / 2}
cy={j * cellSize + cellSize / 2}
fill="var(--ck-qr-dot-color)"
r={cellSize / 3}
/>,
);
}
}
}
});
});

return dotsArray;
}, [ecl, image, imageBackground, logoSize, size, uri]);

return (
<svg
height={size}
width={size}
viewBox={`0 0 ${size} ${size}`}
style={{
width: size,
height: size,
}}
>
<rect fill="transparent" height={size} width={size} />
{dots}
</svg>
);
}

export default QRCodeRenderer;

0 comments on commit dc4901a

Please sign in to comment.