From 7d604fa0bb79f63f23871bccb434ca8196e5378b Mon Sep 17 00:00:00 2001 From: Sidney Alcantara Date: Tue, 18 Feb 2020 18:01:46 +1100 Subject: [PATCH] add FileUploader --- www/src/{Theme.ts => Theme.tsx} | 18 ++- www/src/assets/icons/Action.tsx | 2 +- www/src/assets/icons/Upload.tsx | 11 ++ www/src/components/Confirmation.tsx | 28 ++-- www/src/components/Fields/CheckBox.tsx | 1 + www/src/components/Fields/File.tsx | 13 +- .../SideDrawer/Form/Fields/FileUploader.tsx | 151 ++++++++++++++++++ .../SideDrawer/Form/Fields/ImageUploader.tsx | 1 - www/src/components/SideDrawer/Form/index.tsx | 12 +- 9 files changed, 211 insertions(+), 26 deletions(-) rename www/src/{Theme.ts => Theme.tsx} (90%) create mode 100644 www/src/assets/icons/Upload.tsx create mode 100644 www/src/components/SideDrawer/Form/Fields/FileUploader.tsx diff --git a/www/src/Theme.ts b/www/src/Theme.tsx similarity index 90% rename from www/src/Theme.ts rename to www/src/Theme.tsx index e424cb3a..1169fe0c 100644 --- a/www/src/Theme.ts +++ b/www/src/Theme.tsx @@ -1,4 +1,7 @@ +import React from "react"; + import createMuiTheme from "@material-ui/core/styles/createMuiTheme"; +import ClearIcon from "@material-ui/icons/Clear"; const HEADING_TEXT = "Europa, sans-serif"; const BODY_TEXT = '"Open Sans", sans-serif'; @@ -88,6 +91,11 @@ const Theme = createMuiTheme({ root: { borderRadius: 4, }, + outlined: { + backgroundColor: "rgba(0, 0, 0, 0.08)", + borderColor: "rgba(0, 0, 0, 0.08)", + color: "rgba(0, 0, 0, 0.6)", + }, label: { // overline style fontFamily: HEADING_TEXT, @@ -96,12 +104,14 @@ const Theme = createMuiTheme({ fontStyle: "normal", lineHeight: 1.2, letterSpacing: 1.125, - color: "rgba(0, 0, 0, 0.6)", }, labelSmall: { paddingLeft: 12, paddingRight: 11, }, + deleteIcon: { + color: "inherit", + }, }, MuiPaper: { rounded: { @@ -151,7 +161,11 @@ const Theme = createMuiTheme({ transformOrigin: { vertical: "top", horizontal: "center" }, }, }, - MuiChip: { size: "small" }, + MuiChip: { + size: "small", + variant: "outlined", + deleteIcon: , + }, MuiButton: { color: "primary" }, }, }); diff --git a/www/src/assets/icons/Action.tsx b/www/src/assets/icons/Action.tsx index f055780e..6c9252b9 100644 --- a/www/src/assets/icons/Action.tsx +++ b/www/src/assets/icons/Action.tsx @@ -2,7 +2,7 @@ import React from "react"; import SvgIcon, { SvgIconProps } from "@material-ui/core/SvgIcon"; import { mdiGestureTap } from "@mdi/js"; -export default function SubTable(props: SvgIconProps) { +export default function Action(props: SvgIconProps) { return ( diff --git a/www/src/assets/icons/Upload.tsx b/www/src/assets/icons/Upload.tsx new file mode 100644 index 00000000..59945325 --- /dev/null +++ b/www/src/assets/icons/Upload.tsx @@ -0,0 +1,11 @@ +import * as React from "react"; +import SvgIcon, { SvgIconProps } from "@material-ui/core/SvgIcon"; +import { mdiUpload } from "@mdi/js"; + +export default function Upload(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/www/src/components/Confirmation.tsx b/www/src/components/Confirmation.tsx index 99ae4f6a..65eb0949 100644 --- a/www/src/components/Confirmation.tsx +++ b/www/src/components/Confirmation.tsx @@ -20,7 +20,7 @@ const useStyles = makeStyles(theme => }) ); -interface Props { +export interface IConfirmationProps { children: JSX.Element; message?: { title?: string; @@ -30,9 +30,15 @@ interface Props { confirm?: string | JSX.Element; }; confirmationCommand?: string; + functionName?: string; } -const Confirmation = (props: Props) => { - const { children, message, confirmationCommand } = props; + +export default function Confirmation({ + children, + message, + confirmationCommand, + functionName = "onClick", +}: IConfirmationProps) { const classes = useStyles(); const [showDialog, setShowDialog] = useState(false); const [dryText, setDryText] = useState(""); @@ -40,20 +46,16 @@ const Confirmation = (props: Props) => { const handleClose = () => { setShowDialog(false); }; - //Gets the function of the wrapped button to use for execution on conformation - const confirmHandler = children.props.onChange || children.props.onClick; + + const confirmHandler = children.props[functionName]; const button = React.cloneElement(children, { - onClick: () => { - setShowDialog(true); - }, - onChange: () => { - setShowDialog(true); - }, + [functionName]: () => setShowDialog(true), }); return ( <> {button} + {(message && message.title) || "Are you sure?"} @@ -108,6 +110,4 @@ const Confirmation = (props: Props) => { ); -}; - -export default Confirmation; +} diff --git a/www/src/components/Fields/CheckBox.tsx b/www/src/components/Fields/CheckBox.tsx index 34dec7b1..56199465 100644 --- a/www/src/components/Fields/CheckBox.tsx +++ b/www/src/components/Fields/CheckBox.tsx @@ -26,6 +26,7 @@ const CheckBox = (props: Props) => { row.firstName ), }} + functionName="onChange" > chipList: { overflow: "hidden" }, chipGridItem: { maxWidth: "100%" }, - chip: { cursor: "pointer", width: "100%" }, + chip: { width: "100%" }, uploadButton: { marginLeft: "auto" }, }) ); @@ -87,17 +87,16 @@ const File = (props: Props) => { } label={file.name} - component="a" - target="_blank" - rel="noopener noreferrer" - href={file.downloadURL} + onClick={e => { + window.open(file.downloadURL); + e.stopPropagation(); + }} onDelete={ config && config.isLocked ? undefined : () => handleDelete(file.downloadURL) } className={classes.chip} - onClick={e => e.stopPropagation()} /> )) diff --git a/www/src/components/SideDrawer/Form/Fields/FileUploader.tsx b/www/src/components/SideDrawer/Form/Fields/FileUploader.tsx new file mode 100644 index 00000000..b2fa5ba5 --- /dev/null +++ b/www/src/components/SideDrawer/Form/Fields/FileUploader.tsx @@ -0,0 +1,151 @@ +import React, { useCallback, useState } from "react"; +import clsx from "clsx"; +import { FieldProps } from "formik"; + +import { useDropzone } from "react-dropzone"; +import useUploader, { FileValue } from "hooks/useFiretable/useUploader"; + +import { + makeStyles, + createStyles, + ButtonBase, + Typography, + Grid, + Chip, + CircularProgress, +} from "@material-ui/core"; + +import UploadIcon from "assets/icons/Upload"; +import { FileIcon } from "constants/fields"; + +import ErrorMessage from "../ErrorMessage"; +import Confirmation from "components/Confirmation"; + +const useStyles = makeStyles(theme => + createStyles({ + dropzoneButton: { + backgroundColor: + theme.palette.type === "light" + ? "rgba(0, 0, 0, 0.09)" + : "rgba(255, 255, 255, 0.09)", + borderRadius: theme.shape.borderRadius, + padding: theme.spacing(0, 2), + justifyContent: "flex-start", + + margin: 0, + width: "100%", + height: 56, + + color: theme.palette.text.secondary, + + "& svg": { marginRight: theme.spacing(2) }, + }, + + chipList: { marginTop: theme.spacing(1) }, + chipGridItem: { maxWidth: "100%" }, + chip: { width: "100%" }, + }) +); + +export interface IFileUploaderProps extends FieldProps { + docRef?: firebase.firestore.DocumentReference; +} + +export default function FileUploader({ + form, + field, + docRef, +}: IFileUploaderProps) { + const classes = useStyles(); + + const [uploaderState, upload] = useUploader(); + const { progress } = uploaderState; + + // Store a preview image locally while uploading + const [localFile, setLocalFile] = useState(""); + + const onDrop = useCallback( + (acceptedFiles: File[]) => { + const file = acceptedFiles[0]; + + if (docRef && file) { + upload({ + docRef, + fieldName: field.name, + files: [file], + previousValue: field.value ?? [], + onComplete: newValue => { + form.setFieldValue(field.name, newValue); + setLocalFile(""); + }, + }); + setLocalFile(file.name); + } + }, + [docRef] + ); + + const handleDelete = (index: number) => { + const newValue = [...field.value]; + newValue.splice(index, 1); + form.setFieldValue(field.name, newValue); + }; + + const { getRootProps, getInputProps } = useDropzone({ + onDrop, + multiple: false, + }); + + return ( + <> + + + + + Upload file + + + + + {field.value.map((file: FileValue, i) => ( + + + } + label={file.name} + onClick={() => window.open(file.downloadURL)} + onDelete={() => handleDelete(i)} + className={classes.chip} + /> + + + ))} + + {localFile && ( + + } + label={localFile} + className={classes.chip} + onDelete={() => {}} + deleteIcon={ + + } + /> + + )} + + + + + ); +} diff --git a/www/src/components/SideDrawer/Form/Fields/ImageUploader.tsx b/www/src/components/SideDrawer/Form/Fields/ImageUploader.tsx index 48152209..61c0b9f8 100644 --- a/www/src/components/SideDrawer/Form/Fields/ImageUploader.tsx +++ b/www/src/components/SideDrawer/Form/Fields/ImageUploader.tsx @@ -82,7 +82,6 @@ const useStyles = makeStyles(theme => ); export interface IImageUploaderProps extends FieldProps { - label: React.ReactNode; docRef?: firebase.firestore.DocumentReference; } diff --git a/www/src/components/SideDrawer/Form/index.tsx b/www/src/components/SideDrawer/Form/index.tsx index 4d3aa1ac..080d7f6e 100644 --- a/www/src/components/SideDrawer/Form/index.tsx +++ b/www/src/components/SideDrawer/Form/index.tsx @@ -21,6 +21,7 @@ import Color from "./Fields/Color"; import Slider from "./Fields/Slider"; // import TextMulti from "./Fields/TextMulti"; import ImageUploader from "./Fields/ImageUploader"; +import FileUploader from "./Fields/FileUploader"; import { FieldType } from "constants/fields"; // import Heading from "./Heading"; @@ -200,13 +201,22 @@ export default function Form({ fields, values }: IFormProps) { ); break; + case FieldType.file: + renderedField = ( + + ); + break; + case FieldType.rating: renderedField = ( ); break; - // case FieldType.file: // case FieldType.connectTable: // case FieldType.subTable: // case FieldType.action: