UNPKG

@proca/widget

Version:

Proca is an open-source campaign toolkit designed to empower activists and organisations in their digital advocacy efforts. It provides a flexible and customisable platform for creating and managing online petitions, email campaigns, and other forms of di

279 lines (266 loc) 7.83 kB
import React, { useState, useEffect, useRef, useCallback } from "react"; import { Button, IconButton, Box, LinearProgress, FormHelperText, } from "@material-ui/core"; import PhotoCameraIcon from "@material-ui/icons/PhotoCamera"; import VideocamIcon from "@material-ui/icons/Videocam"; import CameraFrontIcon from "@material-ui/icons/CameraFront"; import CameraRearIcon from "@material-ui/icons/CameraRear"; import { useCampaignConfig } from "@hooks/useConfig"; import { useTranslation } from "react-i18next"; import { useUpload, getBlurhash } from "@components/field/image/Publish"; const CameraField = props => { const [camera, switchCamera] = useState(false); const [isValidating, validating] = useState(false); const [cameras, setCameras] = useState([]); const [dimension, setDimension] = useState({}); const [picture, _takePicture] = useState(undefined); const max_size = 640; const [cDim, _setcDim] = useState({ width: max_size, height: max_size }); // it will be adjusted based on the camera const canvasRef = useRef(); const videoRef = useRef(); const config = useCampaignConfig(); const { t } = useTranslation(); const upload = useUpload(canvasRef, max_size); const { formState: { errors }, getValues, register, setError, setValue, } = props.form ? props.form : { formState: { errors: {} } }; const setcDim = dim => { let { width, height } = dim; if (width > height) { if (width > max_size) { height *= max_size / width; width = max_size; } } else { if (height > max_size) { width *= max_size / height; height = max_size; } } _setcDim({ width: dim.width, height: dim.height }); }; const startCamera = useCallback( async facingMode => { let video = videoRef.current; video.setAttribute("autoplay", ""); video.setAttribute("muted", ""); video.setAttribute("playsinline", ""); let stream = null; let constraint = { audio: false, video: { width: 640, height: 360, facingMode: facingMode || "user", // prefer the rear camera // facingMode: facingMode || "environment", // prefer the rear camera }, }; try { stream = await navigator.mediaDevices.getUserMedia(constraint); } catch (err) { setError("image", { type: "js", message: "camera error, check your permissions\n [" + err.toString() + "[]", }); console.log("can't get camera", err); return; } const devices = await navigator.mediaDevices.enumerateDevices(); const videoDevices = devices .filter(device => device.kind === "videoinput") .map(d => ({ id: d.deviceId, name: d.label })); setCameras(videoDevices); video.srcObject = stream; video.onloadedmetadata = () => { let dim = { width: video.videoWidth, height: video.videoHeight, }; setcDim(dim); setDimension(dim); }; switchCamera(constraint.video.facingMode); }, [setError] ); useEffect(() => { // declare the data fetching function const checkPermissions = async () => { try { const allowed = await navigator.permissions.query({ name: "camera" }); console.log("allowed", allowed); if (allowed.state === "granted") { startCamera("environment"); } } catch { // firefox doesn't allow camera permission to be checked } }; if (cameras.length === 0) { checkPermissions(); } }, [cameras.length, startCamera]); const takePicture = async () => { let video = videoRef.current; let canvas = canvasRef.current; canvas .getContext("2d") .drawImage( video, 0, 0, dimension.width, dimension.height, 0, 0, canvas.width, canvas.height ); let image_data_url = canvas.toDataURL("image/jpeg"); _takePicture(image_data_url); if (props.setCanvas) props.setCanvas(canvas); // data url of the image }; const switchCam = () => { startCamera(camera === "environment" ? "user" : "environment"); }; const validateImage = async () => { // const delay = ms => new Promise(res => setTimeout(res, ms)); //await delay (1500); if (!camera) return "Start the camera and take a picture"; if (!picture) { await takePicture(); //if (!picture) return "Take a picture"; } validating(true); const r = await upload({ hash: getValues("hash") }); if (r.message) { return r.message; } setValue("hash", r.hash); setValue("imageId", r.id); setValue("blurhash", getBlurhash(canvasRef)); setValue( "image", process.env.REACT_APP_SUPABASE_URL + "/storage/v1/object/public/" + config.campaign.name.replaceAll("_", "-") + "/public/" + r.hash + ".jpg" ); validating(false); return true; }; return ( <> {register && ( <> <input type="hidden" {...register("image", { validate: validateImage })} /> <input type="hidden" {...register("hash")} /> <input type="hidden" {...register("imageId")} /> </> )} {!camera && ( <> <Button fullWidth startIcon={<VideocamIcon />} variant="contained" color="primary" onClick={() => startCamera("environment")} > {t("camera.start", "start the camera")} </Button> </> )} <Box fullWidth style={{ position: "relative", maxWidth: "100%", cursor: "pointer" }} > <video hidden={!camera || picture} onClick={takePicture} id="video" ref={videoRef} width="100%" height="auto" autoPlay playsInline muted /> {camera && !picture && ( <Box display="flex"> <Box flexGrow={1}> <Button fullWidth variant="contained" onClick={takePicture} color="primary" startIcon={<PhotoCameraIcon />} > {t("camera.take", "Take picture")} </Button> </Box> <Box> {cameras.length > 1 && ( <IconButton aria-label="switch camera front-back" onClick={switchCam} > {camera === "user" ? <CameraRearIcon /> : <CameraFrontIcon />} </IconButton> )} </Box> </Box> )} </Box> <Box hidden={!picture} fullWidth style={{ maxWidth: "100%", cursor: "pointer", position: "relative" }} onClick={takePicture} > <canvas id="canvas" ref={canvasRef} width={cDim.width} style={{ maxWidth: "100%" }} height={cDim.height} /> {isValidating && <LinearProgress fullWidth />} </Box> {picture && ( <Box> <Button fullWidth variant="contained" startIcon={<PhotoCameraIcon />} onClick={() => _takePicture(undefined)} > {t("camera.take-another", "Take another one")} </Button> </Box> )} <div> <FormHelperText error={!!(errors && errors.image)}> {errors && errors.image && errors.image.message} </FormHelperText> </div> </> ); }; export default CameraField;