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

414 lines (393 loc) 12.1 kB
import React, { useState, useRef, createRef, useCallback } from "react"; import { scrollTo } from "@lib/scroll"; import { Image as KonvaImage, Layer, Stage, } from "react-konva/lib/ReactKonvaCore"; import useImage from "use-image"; import Camera, { useUpload } from "./Camera"; import Hidden from "./Hidden"; import { IndividualSticker } from "./image/sticker"; import UploadPicture from "./image/Upload"; import SelectPicture from "./image/Select"; import ImageIcon from "@material-ui/icons/Image"; import PhotoCameraIcon from "@material-ui/icons/PhotoCamera"; import PhotoLibraryIcon from "@material-ui/icons/PhotoLibrary"; import { useCampaignConfig } from "@hooks/useConfig"; import { resize } from "@lib/image"; import { Grid, Step, Stepper, StepLabel, StepButton, Accordion, AccordionSummary, AccordionDetails, Button, ButtonGroup, CardContent, Card, CardHeader, Collapse, } from "@material-ui/core"; import { Alert, AlertTitle } from "@material-ui/lab"; import ExpandMoreIcon from "@material-ui/icons/ExpandMore"; import Dialog from "@components/Dialog"; import { useTranslation } from "react-i18next"; import { makeStyles } from "@material-ui/core/styles"; const useStyles = makeStyles(theme => ({ imgsticker: { width: "66px", display: "inline", }, sticker: { cursor: "copy", }, accordion: { display: "block!important", }, buttonGroup: { marginRight: theme.spacing(1), }, dialog: { minWidth: theme.breakpoints.values.sm, }, stickers: { maxWidth: theme.breakpoints.values.sm, }, })); export default function ImageStickerComplete(props) { const config = useCampaignConfig(); const { t } = useTranslation(); const classes = useStyles(); const [draw, setDraw] = useState(false); const [image, setImage] = useState(undefined); const [canvas, setCanvas] = useState(undefined); const [activeStep, setActiveStep] = useState(0); const [expanded, setExpanded] = useState(false); // const data = props.form?.getValues(); const handleClose = () => { setDraw(false); scrollTo({ delay: 300, selector: "#proca-image" }); }; const handleChange = panel => (_event, isExpanded) => { setExpanded(isExpanded ? panel : false); }; const handleStep = step => () => { setActiveStep(step); }; const uploadedCanvas = canvas => { setCanvas(canvas); setActiveStep(1); }; return ( <div> <Hidden name="hash" form={props.form} /> <Hidden name="dimension" form={props.form} /> <ImageOption image={image} setImage={setImage} setDraw={setDraw} /> <Dialog name={config.campaign.title} maxWidth="lg" dialog={draw !== false} close={handleClose} hideBackdrop > <Stepper activeStep={activeStep}> <Step key={0}> <StepButton onClick={handleStep(0)}> <StepLabel>{t("image.select", "choose your picture")}</StepLabel> </StepButton> </Step> <Step key={1}> <StepButton> <StepLabel>{t("image.addSticker", "add stickers")}</StepLabel> </StepButton> </Step> </Stepper> {config.component.picture?.upload !== false && ( <> <div hidden={activeStep !== 0} className={classes.dialog}> {t("image.options", "Would you like to")} <Accordion TransitionProps={{ unmountOnExit: true }} expanded={expanded === "upload"} onChange={handleChange("upload")} > <AccordionSummary expandIcon={<ExpandMoreIcon />}> <ImageIcon color="primary" /> {t("image.upload", "Upload a picture")} </AccordionSummary> <AccordionDetails classes={{ root: classes.accordion }}> <UploadPicture setCanvas={uploadedCanvas} /> </AccordionDetails> </Accordion> <Accordion TransitionProps={{ unmountOnExit: true }} expanded={expanded === "webcam"} onChange={handleChange("webcam")} > <AccordionSummary expandIcon={<ExpandMoreIcon />}> <PhotoCameraIcon color="primary" /> {t("image.takeTitle", "Take a picture with your phone")} </AccordionSummary> <AccordionDetails classes={{ root: classes.accordion }}> <Camera setCanvas={uploadedCanvas} form={props.form} /> </AccordionDetails> </Accordion> <Accordion TransitionProps={{ unmountOnExit: true }} expanded={expanded === "select"} onChange={handleChange("select")} > <AccordionSummary expandIcon={<ExpandMoreIcon />}> <PhotoLibraryIcon color="primary" /> {t("image.select")} </AccordionSummary> <AccordionDetails classes={{ root: classes.accordion }}> <SelectPicture setCanvas={uploadedCanvas} /> </AccordionDetails> </Accordion> </div> </> )} {config.component.picture?.upload == false && ( <> <SelectPicture setCanvas={uploadedCanvas} /> </> )} <div hidden={activeStep !== 1} className={classes.dialog}> <ImageStickerKonva setImage={setImage} setDraw={setDraw} backgroundCanvas={canvas} form={props.form} /> </div> </Dialog> {image && ( <img alt="to send to the recipients" style={{ maxWidth: "100%" }} src={image} /> )} </div> ); } const ImageStickerKonva = props => { const config = useCampaignConfig(); const { t } = useTranslation(); const { setValue } = props.form; const [background] = useImage( `${config.component.sticker.baseUrl}/${config.component.sticker.picture}`, "anonymous", "origin" ); let image = undefined; const [images, setImages] = useState([]); const canvasRef = useRef(); const data = props.form?.getValues(); const upload = useUpload(canvasRef, data); const classes = useStyles(); let stickersData = config.component.sticker.data || []; if (props.backgroundCanvas) { image = new Image(); image.src = props.backgroundCanvas.toDataURL(); } if (config.component.sticker.baseUrl) { stickersData = stickersData.map(image => Object.assign({}, image, { url: `${config.component.sticker.baseUrl}/${image.url}`, }) ); } const addStickerToPanel = ({ src, width, x, y }) => { setImages(currentImages => [ ...currentImages, { width, x, y, src, resetButtonRef: createRef(), }, ]); }; const resetAllButtons = useCallback(() => { images.forEach(image => { if (image.resetButtonRef.current) { image.resetButtonRef.current(); } }); }, [images]); const handleCanvasClick = useCallback( event => { if (event.target.attrs.id === "backgroundImage") { resetAllButtons(); } }, [resetAllButtons] ); const handleSave = async close => { if (!close) close = true; canvasRef.current.find("Circle").forEach(d => d.hide()); canvasRef.current.find(".delete").forEach(d => d.hide()); const r = await upload(); console.log("uploaded", r); // r.hash; setValue("hash", r.hash); setValue("dimension", `[${r.width},${r.height}]`); props.setImage(canvasRef.current.toCanvas().toDataURL("image/jpeg", 0.8)); // console.log(canvasRef.current.toDataUrl("jpeg",81)); props.setDraw(!close); }; const { width, height } = props.backgroundCanvas ? resize(props.backgroundCanvas) : resize(background); return ( <> <Stage width={width} height={height} onClick={handleCanvasClick} onTap={handleCanvasClick} ref={canvasRef} > <Layer> {!!image && ( <KonvaImage image={image} height={height} width={width} id="backgroundImage" /> )} {!image && ( <KonvaImage image={background} height={height} width={height} id="backgroundImage" /> )} {images.map((image, i) => { return ( <IndividualSticker onDelete={() => { const newImages = [...images]; newImages.splice(i, 1); setImages(newImages); }} onDragEnd={event => { image.x = event.target.x(); image.y = event.target.y(); }} key={`sticker_${i}`} image={image} /> ); })} </Layer> </Stage> <Card> <CardHeader subheader={t("image.addStickerTitle", "Click/Tap to add a sticker")} /> <CardContent className={classes.stickers}> {stickersData.map((sticker, i) => { return ( <span key={`sticker_${i}`} className={classes.sticker} onMouseDown={() => { addStickerToPanel({ src: sticker.url, width: sticker.width, x: 20 + Math.floor(Math.random() * width - 20), y: 20 + Math.floor(Math.random() * height - 20), }); }} > <img className={classes.imgsticker} alt={sticker.alt} src={sticker.url} width={sticker.width} /> </span> ); })} </CardContent> </Card> <div> <Button color="primary" variant="contained" onClick={handleSave} size="large" > {t("image.publish", "Looks good, publish!")} </Button> {config.test && ( <Button color="secondary" variant="contained" onClick={() => handleSave(false)} size="large" > Publish (debug) </Button> )} </div> </> ); }; const ImageOption = props => { const config = useCampaignConfig(); const { image, setImage, setDraw } = props; const { t } = useTranslation(); const classes = useStyles(); const confirmOptOut = !(config.component?.consent?.benefit === false); // !(config.component.consent?.confirm === false); // by default we ask for confirmation, same as for Consent return ( <Grid container spacing={1} justifyContent="space-between"> <Grid item id="proca-image"> {t("image.wanttoadd")} </Grid> <Grid item> <ButtonGroup variant="contained" color="primary" className={classes.buttonGroup} > <Button disableElevation={!!image} color={image === false ? "default" : "primary"} onClick={() => { setImage(undefined); setDraw(true); }} > {t("yes")} </Button> <Button variant="contained" onClick={() => setImage(false)} color={image ? "default" : "primary"} > {t("no")} </Button> </ButtonGroup> </Grid> {confirmOptOut && ( <Collapse in={image === false}> <Alert severity="info" icon={<ImageIcon />}> <AlertTitle>{t("confirm", "Are you sure?")}</AlertTitle> <span>{t("image.benefit")}</span> </Alert> </Collapse> )} </Grid> ); };