PromucFlow_constructor/app/client/src/components/ads/FilePicker.tsx

354 lines
9.2 KiB
TypeScript
Raw Normal View History

import React, { useEffect, useRef, useState } from "react";
import styled from "styled-components";
import Button, { Category, Size } from "./Button";
import axios from "axios";
import { ReactComponent as UploadIcon } from "../../assets/icons/ads/upload.svg";
import { DndProvider, useDrop, DropTargetMonitor } from "react-dnd";
import HTML5Backend, { NativeTypes } from "react-dnd-html5-backend";
import Text, { TextType } from "./Text";
import { Classes, Variant } from "./common";
import { Toaster } from "./Toast";
const CLOUDINARY_PRESETS_NAME = "";
const CLOUDINARY_CLOUD_NAME = "";
type FilePickerProps = {
onFileUploaded?: (fileUrl: string) => void;
onFileRemoved?: () => void;
fileUploader?: FileUploader;
url?: string;
logoUploadError?: string;
};
const ContainerDiv = styled.div<{
isUploaded: boolean;
isActive: boolean;
canDrop: boolean;
}>`
width: 320px;
height: 190px;
2020-12-24 04:32:25 +00:00
background-color: ${(props) => props.theme.colors.filePicker.bg};
position: relative;
#fileInput {
display: none;
}
.drag-drop-text {
2020-12-24 04:32:25 +00:00
margin: ${(props) => props.theme.spaces[6]}px 0
${(props) => props.theme.spaces[6]}px 0;
color: ${(props) => props.theme.colors.filePicker.color};
}
.bg-image {
width: 100%;
height: 100%;
display: grid;
place-items: center;
background-repeat: no-repeat;
background-position: center;
background-size: contain;
}
.file-description {
width: 95%;
margin-top: auto;
2020-12-24 04:32:25 +00:00
margin-bottom: ${(props) => props.theme.spaces[6] + 1}px;
display: none;
}
.file-spec {
2020-12-24 04:32:25 +00:00
margin-bottom: ${(props) => props.theme.spaces[2]}px;
span {
2020-12-24 04:32:25 +00:00
margin-right: ${(props) => props.theme.spaces[4]}px;
}
}
.progress-container {
width: 100%;
2020-12-24 04:32:25 +00:00
background: ${(props) => props.theme.colors.filePicker.progress};
transition: height 0.2s;
}
.progress-inner {
2020-12-24 04:32:25 +00:00
background-color: ${(props) => props.theme.colors.success.light};
transition: width 0.4s ease;
2020-12-24 04:32:25 +00:00
height: ${(props) => props.theme.spaces[1]}px;
border-radius: ${(props) => props.theme.spaces[1] - 1}px;
width: 0%;
}
.button-wrapper {
display: flex;
flex-direction: column;
align-items: center;
}
.remove-button {
display: none;
position: absolute;
bottom: 0;
right: 0;
background: linear-gradient(
180deg,
2020-12-24 04:32:25 +00:00
${(props) => props.theme.colors.filePicker.shadow.from},
${(props) => props.theme.colors.filePicker.shadow.to}
);
opacity: 0.6;
width: 100%;
a {
width: 110px;
2020-12-24 04:32:25 +00:00
margin: ${(props) => props.theme.spaces[13]}px
${(props) => props.theme.spaces[3]}px
${(props) => props.theme.spaces[3]}px auto;
.${Classes.ICON} {
2020-12-24 04:32:25 +00:00
margin-right: ${(props) => props.theme.spaces[2] - 1}px;
}
}
}
&:hover {
.remove-button {
2020-12-24 04:32:25 +00:00
display: ${(props) => (props.isUploaded ? "block" : "none")};
}
}
`;
export type SetProgress = (percentage: number) => void;
export type UploadCallback = (url: string) => void;
export type FileUploader = (
file: any,
setProgress: SetProgress,
onUpload: UploadCallback,
) => void;
export function CloudinaryUploader(
file: any,
setProgress: SetProgress,
onUpload: UploadCallback,
) {
const formData = new FormData();
formData.append("upload_preset", CLOUDINARY_PRESETS_NAME);
if (file) {
formData.append("file", file);
}
axios
.post(
`https://api.cloudinary.com/v1_1/${CLOUDINARY_CLOUD_NAME}/image/upload`,
formData,
{
headers: {
"Content-Type": "multipart/form-data",
},
onUploadProgress: function(progressEvent: ProgressEvent) {
const uploadPercentage = Math.round(
(progressEvent.loaded / progressEvent.total) * 100,
);
setProgress(uploadPercentage);
},
},
)
2020-12-24 04:32:25 +00:00
.then((data) => {
onUpload(data.data.url);
})
2020-12-24 04:32:25 +00:00
.catch((error) => {
console.error("error in file uploading", error);
});
}
const FilePickerComponent = (props: FilePickerProps) => {
const { logoUploadError } = props;
const [fileInfo, setFileInfo] = useState<{ name: string; size: number }>({
name: "",
size: 0,
});
const [isUploaded, setIsUploaded] = useState<boolean>(false);
const [fileUrl, setFileUrl] = useState("");
const [{ canDrop, isOver }, drop] = useDrop({
accept: [NativeTypes.FILE],
drop(item, monitor) {
onDrop(monitor);
},
2020-12-24 04:32:25 +00:00
collect: (monitor) => ({
isOver: monitor.isOver(),
canDrop: monitor.canDrop(),
}),
});
const inputRef = useRef<HTMLInputElement>(null);
const bgRef = useRef<HTMLDivElement>(null);
const progressRef = useRef<HTMLDivElement>(null);
const fileDescRef = useRef<HTMLDivElement>(null);
const fileContainerRef = useRef<HTMLDivElement>(null);
function ButtonClick(event: React.MouseEvent<HTMLElement>) {
event.preventDefault();
if (inputRef.current) {
inputRef.current.click();
}
}
function onDrop(monitor: DropTargetMonitor) {
if (monitor) {
const files = monitor.getItem().files;
if (files) {
handleFileUpload(files);
}
}
}
function setProgress(uploadPercentage: number) {
if (progressRef.current) {
progressRef.current.style.width = `${uploadPercentage}%`;
}
if (uploadPercentage === 100) {
setIsUploaded(true);
if (fileDescRef.current && bgRef.current) {
fileDescRef.current.style.display = "none";
bgRef.current.style.opacity = "1";
}
}
}
function onUpload(url: string) {
props.onFileUploaded && props.onFileUploaded(url);
}
function handleFileUpload(files: FileList | null) {
const file = files && files[0];
let fileSize = 0;
if (file) {
fileSize = Math.floor(file.size / 1024);
setFileInfo({ name: file.name, size: fileSize });
}
if (fileSize < 250) {
if (bgRef.current) {
bgRef.current.style.backgroundImage = `url(${URL.createObjectURL(
file,
)})`;
bgRef.current.style.opacity = "0.5";
}
if (fileDescRef.current) {
fileDescRef.current.style.display = "block";
}
if (fileContainerRef.current) {
fileContainerRef.current.style.display = "none";
}
/* set form data and send api request */
props.fileUploader && props.fileUploader(file, setProgress, onUpload);
} else {
Toaster.show({
text: "File size should be less than 250kb!",
variant: Variant.warning,
});
}
}
function removeFile() {
if (fileContainerRef.current && bgRef.current) {
setFileUrl("");
fileContainerRef.current.style.display = "flex";
bgRef.current.style.backgroundImage = "url('')";
setIsUploaded(false);
props.onFileRemoved && props.onFileRemoved();
}
}
const isActive = canDrop && isOver;
useEffect(() => {
if (props.url) {
const urlKeys = props.url.split("/");
if (urlKeys[urlKeys.length - 1] !== "null") {
setFileUrl(props.url);
} else {
setFileUrl("");
}
}
}, [props.url]);
useEffect(() => {
if (fileUrl && !isUploaded) {
setIsUploaded(true);
if (bgRef.current) {
bgRef.current.style.backgroundImage = `url(${fileUrl})`;
bgRef.current.style.opacity = "1";
}
if (fileDescRef.current) {
fileDescRef.current.style.display = "none";
}
if (fileContainerRef.current) {
fileContainerRef.current.style.display = "none";
}
}
}, [fileUrl, logoUploadError]);
return (
<ContainerDiv
isActive={isActive}
canDrop={canDrop}
isUploaded={isUploaded}
ref={drop}
>
<div ref={bgRef} className="bg-image">
<div className="button-wrapper" ref={fileContainerRef}>
<UploadIcon />
<Text type={TextType.P2} className="drag-drop-text">
Drag & Drop files to upload or
</Text>
<form>
<input
type="file"
id="fileInput"
multiple={false}
ref={inputRef}
accept=".jpeg,.png,.svg"
value={""}
2020-12-24 04:32:25 +00:00
onChange={(el) => handleFileUpload(el.target.files)}
/>
<Button
text="Browse"
category={Category.tertiary}
size={Size.medium}
2020-12-24 04:32:25 +00:00
onClick={(el) => ButtonClick(el)}
/>
</form>
</div>
<div className="file-description" ref={fileDescRef} id="fileDesc">
<div className="file-spec">
<Text type={TextType.H6}>{fileInfo.name}</Text>
<Text type={TextType.H6}>{fileInfo.size}KB</Text>
</div>
<div className="progress-container">
<div className="progress-inner" ref={progressRef}></div>
</div>
</div>
</div>
<div className="remove-button">
<Button
text="remove"
icon="delete"
size={Size.medium}
category={Category.tertiary}
2020-12-24 04:32:25 +00:00
onClick={(el) => removeFile()}
/>
</div>
</ContainerDiv>
);
};
const FilePicker = (props: FilePickerProps) => {
return (
<DndProvider backend={HTML5Backend}>
<FilePickerComponent {...props} />
</DndProvider>
);
};
export default FilePicker;