Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: permissions button on the QR Scanner #729

Merged
merged 27 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
a3468b1
Staff Badge Component
Darguima Dec 22, 2023
5e76371
Add go to top button
Darguima Dec 22, 2023
a827b99
Ran formatter
Darguima Dec 22, 2023
1b0dda6
Merge branch 'main' of github.com:cesium/seium.org into dg/fix-badge-…
Darguima Dec 22, 2023
9a7df66
small fix
Darguima Dec 22, 2023
934ee8c
fix types
Darguima Dec 22, 2023
b67aac6
Merge branch 'main' of github.com:cesium/seium.org into dg/enable_per…
Darguima Jan 13, 2024
39ebd3d
converted to ts
Darguima Jan 13, 2024
d2dcc9a
Merge branch 'main' into dg/enable_permissions_button_scanner
Darguima Jan 29, 2024
033e7df
Rewrite BarebonesQRScanner to better performance
Darguima Jan 29, 2024
0f4715d
Refactoring components that use QRScanner
Darguima Jan 29, 2024
d4f2a45
Merge branch 'main' of github.com:cesium/seium.org into dg/enable_per…
Darguima Feb 6, 2024
c750044
minor fixes
Darguima Feb 7, 2024
076c387
..
Darguima Feb 7, 2024
5935ab6
Fix component behavior
Darguima Feb 18, 2024
82e9b04
improve scanner behavior
Darguima Feb 19, 2024
7d5e33e
cam skeleton
Darguima Feb 19, 2024
64faf41
split barebone in 3 files
Darguima Feb 20, 2024
5c66d2c
searching for permissions before request
Darguima Feb 22, 2024
6a82e66
using Permissions API when available (not on Firefox, for example)
Darguima Feb 26, 2024
e7e1173
css minor fix
Darguima Feb 27, 2024
d2c148e
formatter
Darguima Feb 27, 2024
416a4cc
center cam
Darguima Feb 27, 2024
dd4768f
formatter
Darguima Feb 27, 2024
d90cb6e
fix Firefox temporary permission issue
Darguima Feb 27, 2024
f370c87
formatter
Darguima Feb 27, 2024
c9ae495
add @ts-ignore to ignore incomplete type
Darguima Feb 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion components/Layout/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export default function Layout({ title, description, children }: LayoutProps) {
</button>

{/* CONTENT */}
<main className="w-full px-4 pb-6 pt-20 lg:ml-72 lg:px-20">
<main className="flex min-h-screen w-full flex-col px-4 pb-6 pt-20 lg:ml-72 lg:px-20">
<h2 className="select-none font-ibold text-4xl sm:text-5xl">
{title}
</h2>
Expand Down
136 changes: 0 additions & 136 deletions components/QRScanner/BarebonesQRScanner/index.jsx

This file was deleted.

104 changes: 104 additions & 0 deletions components/QRScanner/BarebonesQRScanner/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import {
useRef,
useState,
useEffect,
MutableRefObject,
ReactNode,
} from "react";
import { FEEDBACK, FeedbackType } from "@components/QRScanner";
import useWebcamPermissions from "./useWebcam";
import useQRScanner from "./useQRScanner";

interface Props extends React.HTMLProps<HTMLDivElement> {
handleQRCode: (uuid: string) => void;
isScanPaused: MutableRefObject<boolean>;
unpauseTimeout?: number;
setScanFeedback?: (feedback: FeedbackType) => void;
}

const BarebonesQRScanner: React.FC<Props> = ({
handleQRCode,
isScanPaused,
unpauseTimeout = 700,
setScanFeedback = (_) => {},
...rest
}) => {
const [successReadingCode, setSuccessReadingCode] = useState(false);
const [camMessage, setCamMessage] = useState<ReactNode>("");
const [isCamReady, setIsCamReady] = useState(false);

const videoRef = useRef<HTMLVideoElement>(null);
const canvasRef = useRef<HTMLCanvasElement>(null);
const animationFrameRef = useRef<number>();

const parseURL = (url: string) => {
try {
const url_obj = new URL(url);

if (url_obj.host !== process.env.NEXT_PUBLIC_QRCODE_HOST) {
setScanFeedback(FEEDBACK.INVALID_QR);
return null;
}

return url_obj.pathname.split("/").at(-1);
} catch {
return null;
}
};

useEffect(() => {
if (!successReadingCode) {
const timeoutId = setTimeout(() => {
setScanFeedback(FEEDBACK.SCANNING);
isScanPaused.current = false;
}, unpauseTimeout);

return () => {
clearTimeout(timeoutId);
};
}
}, [successReadingCode]);

useWebcamPermissions({
videoRef,
onPermissionGranted: () => {
setIsCamReady(true);
},
setCamMessage,
});

useQRScanner({
isCamReady,
videoRef,
canvasRef,
animationFrameRef,
isScanPaused,
parseURL,
handleQRCode,
setSuccessReadingCode,
});

return (
<div
{...rest}
className={
"relative flex aspect-square w-full items-center justify-center overflow-hidden rounded-2xl bg-primary " +
rest.className
}
>
<div className="absolute h-full w-full bg-white opacity-5" />

<video ref={videoRef} className="absolute h-full w-full object-cover" />
<canvas
ref={canvasRef}
className="absolute h-full w-full rounded-2xl object-cover"
/>

<div className="absolute flex h-full w-full items-center justify-center">
<div className="p-16 text-center text-white">{camMessage}</div>
</div>
</div>
);
};

export default BarebonesQRScanner;
97 changes: 97 additions & 0 deletions components/QRScanner/BarebonesQRScanner/useQRScanner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { MutableRefObject, useEffect } from "react";
import jsQR from "jsqr";

interface useQRScannerProps {
isCamReady: boolean;
videoRef: MutableRefObject<HTMLVideoElement>;
canvasRef: MutableRefObject<HTMLCanvasElement>;
animationFrameRef: MutableRefObject<number>;
isScanPaused: MutableRefObject<boolean>;
parseURL: (url: string) => string | null;
handleQRCode: (uuid: string) => void;
setSuccessReadingCode: React.Dispatch<React.SetStateAction<boolean>>;
}

const useQRScanner = ({
isCamReady,
videoRef,
canvasRef,
animationFrameRef,
isScanPaused,
parseURL,
handleQRCode,
setSuccessReadingCode,
}: useQRScannerProps) => {
useEffect(() => {
drawQRBoundingBox();
}, [isCamReady]);

const drawQRBoundingBox = () => {
const video = videoRef?.current;
const canvas = canvasRef?.current;

if (!video || !canvas) {
cancelAnimationFrame(animationFrameRef.current);
return null;
}

const canvas2D = canvas.getContext("2d");
let successReadingCode = false;

if (video.readyState === video.HAVE_ENOUGH_DATA) {
canvas.height = video.videoHeight;
canvas.width = video.videoWidth;

// Will use the canvas to get the video image data, and pass it to jsQR, but will then clear the canvas to just draw the bounding box
canvas2D.drawImage(video, 0, 0, canvas.width, canvas.height);
const imageData = canvas2D.getImageData(
0,
0,
canvas.width,
canvas.height
);
const code = jsQR(imageData.data, imageData.width, imageData.height, {
inversionAttempts: "dontInvert",
});
canvas2D.clearRect(0, 0, canvas.width, canvas.height);

if (code) {
successReadingCode = true;

const {
topLeftCorner,
topRightCorner,
bottomLeftCorner,
bottomRightCorner,
} = code.location;

canvas2D.beginPath();

canvas2D.moveTo(topLeftCorner.x, topLeftCorner.y);
canvas2D.lineTo(topRightCorner.x, topRightCorner.y);
canvas2D.lineTo(bottomRightCorner.x, bottomRightCorner.y);
canvas2D.lineTo(bottomLeftCorner.x, bottomLeftCorner.y);
canvas2D.lineTo(topLeftCorner.x, topLeftCorner.y);

canvas2D.lineWidth = 4;
canvas2D.strokeStyle = "#78f400";
canvas2D.stroke();

if (!isScanPaused.current) {
const uuid = parseURL(code.data);

if (uuid) {
handleQRCode(uuid);
isScanPaused.current = true;
}
}
}
}

setSuccessReadingCode(successReadingCode);

animationFrameRef.current = requestAnimationFrame(drawQRBoundingBox);
};
};

export default useQRScanner;
Loading
Loading